was working, changes to displays

This commit is contained in:
2025-07-10 02:52:55 -05:00
parent 2b63f4c9c7
commit ab2060c711
6 changed files with 116 additions and 24 deletions

View File

@ -1,3 +1,4 @@
<!-- File: app/templates/core/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
@ -119,11 +120,15 @@
Orphaned Media
</a>
</li>
<li>
<a class="dropdown-item" href="{{ url_for('admin.list_invitations') }}">
Invitations
</a>
</li>
</ul>
</div>
{% endif %}
<!-- Plugins dropdown -->
<div class="dropdown me-3">
<a

BIN
celerybeat-schedule Normal file

Binary file not shown.

View File

@ -10,7 +10,7 @@ from sqlalchemy import func, desc
from datetime import datetime, timedelta
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.models import Plant
from plugins.media.models import Media
@ -26,9 +26,9 @@ def dashboard():
if current_user.role != 'admin':
return "Access denied", 403
now = datetime.utcnow()
week_ago = now - timedelta(days=7)
month_ago= now - timedelta(days=30)
now = datetime.utcnow()
week_ago = now - timedelta(days=7)
month_ago = now - timedelta(days=30)
# ─── Overview metrics ────────────────────────────────────────────── #
@ -51,7 +51,7 @@ def dashboard():
# Signups last 30 days
signup_dates = [(month_ago + timedelta(days=i)).date() for i in range(31)]
signup_counts = [
User.query.filter(func.date(User.created_at)==d).count()
User.query.filter(func.date(User.created_at) == d).count()
for d in signup_dates
]
chart_dates = [d.strftime('%Y-%m-%d') for d in signup_dates]
@ -64,7 +64,7 @@ def dashboard():
# ─── Analytics aggregates ─────────────────────────────────────────── #
ev_q = AnalyticsEvent.query.filter(AnalyticsEvent.timestamp >= week_ago)
ev_q = AnalyticsEvent.query.filter(AnalyticsEvent.timestamp >= week_ago)
total_ev = ev_q.count() or 1
error_ev = ev_q.filter(AnalyticsEvent.status_code >= 500).count()
error_pct = round(error_ev / total_ev * 100, 1)
@ -96,10 +96,14 @@ def dashboard():
)
for ua, cnt in ua_counts:
b = 'Other'
if 'Chrome' in ua: b = 'Chrome'
elif 'Firefox' in ua: b = 'Firefox'
elif 'Safari' in ua and 'Chrome' not in ua: b = 'Safari'
elif 'Edge' in ua: b = 'Edge'
if 'Chrome' in ua:
b = 'Chrome'
elif 'Firefox' in ua:
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
referrers = dict(
@ -116,16 +120,14 @@ def dashboard():
# ─── Stats metrics ────────────────────────────────────────────────── #
# Plant totals
total_plants = Plant.query.count()
total_plants = Plant.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(
Plant.plant_type,
func.count(Plant.id).label('count')
).filter(Plant.is_active==True) \
).filter(Plant.is_active == True) \
.group_by(Plant.plant_type) \
.order_by(desc('count')) \
.limit(5) \
@ -340,7 +342,27 @@ def undelete_user(user_id):
@bp.route('/orphaned-media')
@login_required
def orphaned_media_list():
if not current_user.role == 'admin':
abort(403)
if current_user.role != 'admin':
return "Access denied", 403
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
)

View 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 %}

View File

@ -1,3 +1,4 @@
{# File: plugins/admin/templates/admin/users/form.html #}
{% extends 'core/base.html' %}
{% block title %}{{ action }} User Admin Nature In Pots{% endblock %}
@ -37,6 +38,14 @@
{{ form.excluded_from_analytics.label(class="form-check-label") }}
</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>
<div class="mb-3">

View File

@ -1,3 +1,4 @@
{# File: plugins/admin/templates/admin/users/list.html #}
{% extends 'core/base.html' %}
{% block title %}Users Admin Nature In Pots{% endblock %}
{% block content %}
@ -36,9 +37,15 @@
<table class="table table-striped">
<thead>
<tr>
<th>ID</th><th>Email</th><th>Role</th>
<th>Verified</th><th>Excluded</th><th>Status</th>
<th>Joined</th><th>Actions</th>
<th>ID</th>
<th>Email</th>
<th>Role</th>
<th>Verified</th>
<th>Excluded</th>
<th>Status</th>
<th>Joined</th>
<th>Invites Remaining</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="userTableBody">
@ -61,6 +68,7 @@
{% endif %}
</td>
<td>{{ u.created_at.strftime('%Y-%m-%d') }}</td>
<td>{{ u.invites_remaining }}</td>
<td>
<a href="{{ url_for('admin.edit_user',
user_id=u.id,
@ -68,6 +76,13 @@
show_deleted='1' if show_deleted else None,
q=q) }}"
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 %}
<form action="{{ url_for('admin.undelete_user',
user_id=u.id,
@ -168,7 +183,6 @@
</ul>
</nav>
{# --- AJAX search script --- #}
<script>
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('searchInput');
@ -218,15 +232,17 @@ document.addEventListener('DOMContentLoaded', function() {
<td>${u.excluded_from_analytics ? '✓' : ''}</td>
<td>${statusHtml}</td>
<td>${u.created_at}</td>
<td>${u.invites_remaining}</td>
<td>
<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}
</td>
</tr>
`);
});
// hide pagination when searching
paginationNav.style.display = q ? 'none' : '';
});
}