was working, changes to displays
This commit is contained in:
@ -1,3 +1,4 @@
|
|||||||
|
<!-- File: app/templates/core/base.html -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -119,11 +120,15 @@
|
|||||||
Orphaned Media
|
Orphaned Media
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="{{ url_for('admin.list_invitations') }}">
|
||||||
|
Invitations
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<!-- Plugins dropdown -->
|
<!-- Plugins dropdown -->
|
||||||
<div class="dropdown me-3">
|
<div class="dropdown me-3">
|
||||||
<a
|
<a
|
||||||
|
BIN
celerybeat-schedule
Normal file
BIN
celerybeat-schedule
Normal file
Binary file not shown.
@ -10,7 +10,7 @@ from sqlalchemy import func, desc
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from plugins.auth.models import User
|
from plugins.auth.models import User, Invitation
|
||||||
from plugins.plant.growlog.models import GrowLog
|
from plugins.plant.growlog.models import GrowLog
|
||||||
from plugins.plant.models import Plant
|
from plugins.plant.models import Plant
|
||||||
from plugins.media.models import Media
|
from plugins.media.models import Media
|
||||||
@ -96,10 +96,14 @@ def dashboard():
|
|||||||
)
|
)
|
||||||
for ua, cnt in ua_counts:
|
for ua, cnt in ua_counts:
|
||||||
b = 'Other'
|
b = 'Other'
|
||||||
if 'Chrome' in ua: b = 'Chrome'
|
if 'Chrome' in ua:
|
||||||
elif 'Firefox' in ua: b = 'Firefox'
|
b = 'Chrome'
|
||||||
elif 'Safari' in ua and 'Chrome' not in ua: b = 'Safari'
|
elif 'Firefox' in ua:
|
||||||
elif 'Edge' in ua: b = 'Edge'
|
b = 'Firefox'
|
||||||
|
elif 'Safari' in ua and 'Chrome' not in ua:
|
||||||
|
b = 'Safari'
|
||||||
|
elif 'Edge' in ua:
|
||||||
|
b = 'Edge'
|
||||||
browsers[b] = browsers.get(b, 0) + cnt
|
browsers[b] = browsers.get(b, 0) + cnt
|
||||||
|
|
||||||
referrers = dict(
|
referrers = dict(
|
||||||
@ -116,12 +120,10 @@ def dashboard():
|
|||||||
|
|
||||||
# ─── Stats metrics ────────────────────────────────────────────────── #
|
# ─── Stats metrics ────────────────────────────────────────────────── #
|
||||||
|
|
||||||
# Plant totals
|
|
||||||
total_plants = Plant.query.count()
|
total_plants = Plant.query.count()
|
||||||
total_images = Media.query.count()
|
total_images = Media.query.count()
|
||||||
active_plants = Plant.query.filter_by(is_active=True).count()
|
active_plants = Plant.query.filter_by(is_active=True).count()
|
||||||
|
|
||||||
# Top 5 popular plant types
|
|
||||||
popular_plants = db.session.query(
|
popular_plants = db.session.query(
|
||||||
Plant.plant_type,
|
Plant.plant_type,
|
||||||
func.count(Plant.id).label('count')
|
func.count(Plant.id).label('count')
|
||||||
@ -340,7 +342,27 @@ def undelete_user(user_id):
|
|||||||
@bp.route('/orphaned-media')
|
@bp.route('/orphaned-media')
|
||||||
@login_required
|
@login_required
|
||||||
def orphaned_media_list():
|
def orphaned_media_list():
|
||||||
if not current_user.role == 'admin':
|
if current_user.role != 'admin':
|
||||||
abort(403)
|
return "Access denied", 403
|
||||||
items = Media.query.filter_by(status='orphaned').order_by(Media.orphaned_at.desc()).all()
|
items = Media.query.filter_by(status='orphaned').order_by(Media.orphaned_at.desc()).all()
|
||||||
return render_template('admin/orphaned_media_list.html', items=items)
|
return render_template('admin/orphaned_media_list.html', items=items)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Invitation Management ──────────────────────────────────────────────── #
|
||||||
|
|
||||||
|
@bp.route('/invitations')
|
||||||
|
@login_required
|
||||||
|
def list_invitations():
|
||||||
|
if current_user.role != 'admin':
|
||||||
|
return "Access denied", 403
|
||||||
|
|
||||||
|
user_id = request.args.get('user_id', type=int)
|
||||||
|
query = Invitation.query
|
||||||
|
if user_id:
|
||||||
|
query = query.filter(Invitation.created_by == user_id)
|
||||||
|
invitations = query.order_by(Invitation.created_at.desc()).all()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'admin/invitations/list.html',
|
||||||
|
invitations=invitations
|
||||||
|
)
|
||||||
|
40
plugins/admin/templates/admin/invitations/list.html
Normal file
40
plugins/admin/templates/admin/invitations/list.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{% extends 'core/base.html' %}
|
||||||
|
{% block title %}Invitations – Admin – Nature In Pots{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Invitations</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="{{ url_for('auth.send_invite') }}"
|
||||||
|
class="btn btn-primary mb-3">
|
||||||
|
Create Invitation
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Recipient Email</th>
|
||||||
|
<th>Created By</th>
|
||||||
|
<th>Created At</th>
|
||||||
|
<th>Used By</th>
|
||||||
|
<th>Used At</th>
|
||||||
|
<th>Active</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for inv in invitations %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ inv.code }}</td>
|
||||||
|
<td>{{ inv.recipient_email }}</td>
|
||||||
|
<td>{{ inv.creator.email }}</td>
|
||||||
|
<td>{{ inv.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||||
|
<td>{{ inv.user.email if inv.user else '' }}</td>
|
||||||
|
<td>{{ inv.used_at.strftime('%Y-%m-%d %H:%M') if inv.used_at else '' }}</td>
|
||||||
|
<td>{{ 'Yes' if inv.is_active else 'No' }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
@ -1,3 +1,4 @@
|
|||||||
|
{# File: plugins/admin/templates/admin/users/form.html #}
|
||||||
{% extends 'core/base.html' %}
|
{% extends 'core/base.html' %}
|
||||||
{% block title %}{{ action }} User – Admin – Nature In Pots{% endblock %}
|
{% block title %}{{ action }} User – Admin – Nature In Pots{% endblock %}
|
||||||
|
|
||||||
@ -37,6 +38,14 @@
|
|||||||
{{ form.excluded_from_analytics.label(class="form-check-label") }}
|
{{ form.excluded_from_analytics.label(class="form-check-label") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
{{ form.invites_remaining.label(class="form-label") }}
|
||||||
|
{{ form.invites_remaining(class="form-control") }}
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
How many invitations this user may still send.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# File: plugins/admin/templates/admin/users/list.html #}
|
||||||
{% extends 'core/base.html' %}
|
{% extends 'core/base.html' %}
|
||||||
{% block title %}Users – Admin – Nature In Pots{% endblock %}
|
{% block title %}Users – Admin – Nature In Pots{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -36,9 +37,15 @@
|
|||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th><th>Email</th><th>Role</th>
|
<th>ID</th>
|
||||||
<th>Verified</th><th>Excluded</th><th>Status</th>
|
<th>Email</th>
|
||||||
<th>Joined</th><th>Actions</th>
|
<th>Role</th>
|
||||||
|
<th>Verified</th>
|
||||||
|
<th>Excluded</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Joined</th>
|
||||||
|
<th>Invites Remaining</th>
|
||||||
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="userTableBody">
|
<tbody id="userTableBody">
|
||||||
@ -61,6 +68,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ u.created_at.strftime('%Y-%m-%d') }}</td>
|
<td>{{ u.created_at.strftime('%Y-%m-%d') }}</td>
|
||||||
|
<td>{{ u.invites_remaining }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('admin.edit_user',
|
<a href="{{ url_for('admin.edit_user',
|
||||||
user_id=u.id,
|
user_id=u.id,
|
||||||
@ -68,6 +76,13 @@
|
|||||||
show_deleted='1' if show_deleted else None,
|
show_deleted='1' if show_deleted else None,
|
||||||
q=q) }}"
|
q=q) }}"
|
||||||
class="btn btn-sm btn-primary">Edit</a>
|
class="btn btn-sm btn-primary">Edit</a>
|
||||||
|
|
||||||
|
<a href="{{ url_for('auth.adjust_invites', user_id=u.id) }}"
|
||||||
|
class="btn btn-sm btn-info">Adjust Invites</a>
|
||||||
|
|
||||||
|
<a href="{{ url_for('admin.list_invitations', user_id=u.id) }}"
|
||||||
|
class="btn btn-sm btn-warning">Invitations</a>
|
||||||
|
|
||||||
{% if u.is_deleted %}
|
{% if u.is_deleted %}
|
||||||
<form action="{{ url_for('admin.undelete_user',
|
<form action="{{ url_for('admin.undelete_user',
|
||||||
user_id=u.id,
|
user_id=u.id,
|
||||||
@ -168,7 +183,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{# --- AJAX search script --- #}
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const searchInput = document.getElementById('searchInput');
|
const searchInput = document.getElementById('searchInput');
|
||||||
@ -218,15 +232,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<td>${u.excluded_from_analytics ? '✓' : ''}</td>
|
<td>${u.excluded_from_analytics ? '✓' : ''}</td>
|
||||||
<td>${statusHtml}</td>
|
<td>${statusHtml}</td>
|
||||||
<td>${u.created_at}</td>
|
<td>${u.created_at}</td>
|
||||||
|
<td>${u.invites_remaining}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="/admin/users/${u.id}/edit" class="btn btn-sm btn-primary">Edit</a>
|
<a href="/admin/users/${u.id}/edit" class="btn btn-sm btn-primary">Edit</a>
|
||||||
|
<a href="/admin/users/${u.id}/adjust-invites" class="btn btn-sm btn-info">Adjust Invites</a>
|
||||||
|
<a href="/admin/invitations?user_id=${u.id}" class="btn btn-sm btn-warning">Invitations</a>
|
||||||
${actionForms}
|
${actionForms}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// hide pagination when searching
|
|
||||||
paginationNav.style.display = q ? 'none' : '';
|
paginationNav.style.display = q ? 'none' : '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user