Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions app/src/api/backoffice.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,37 @@ async def backoffice_events(
"unique_packages_from_events": unique_packages_from_events or 0,
},
)


@router.get("/packages", response_class=HTMLResponse)
async def backoffice_packages(
request: Request,
db: AsyncSession = Depends(get_db),
admin_user_id: int = Depends(require_admin),
):
"""List all tracked packages with statistics."""

# Query packages with aggregated stats
packages_result = await db.execute(
select(
AnalyticsEvent.package_name,
func.count(AnalyticsEvent.id).label("nr_events"),
func.max(AnalyticsEvent.received_at).label("last_ingested"),
)
.group_by(AnalyticsEvent.package_name)
.order_by(func.count(AnalyticsEvent.id).desc())
)
packages = packages_result.all()

total_packages = len(packages)
total_events = sum(p.nr_events for p in packages)

return templates.TemplateResponse(
"backoffice/packages.html",
{
"request": request,
"packages": packages,
"total_packages": total_packages,
"total_events": total_events,
},
)
4 changes: 4 additions & 0 deletions app/src/templates/backoffice/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
<li><a href="/backoffice/api-keys" class="{% if '/api-keys' in request.url.path %}active{% endif %}">
<i class="fas fa-key"></i> API Keys
</a></li>
<li><a href="/backoffice/packages" class="{% if '/packages' in request.url.path %}active{% endif %}">
<i class="fas fa-box"></i> Packages
</a></li>
<li><a href="/backoffice/events" class="{% if '/events' in request.url.path %}active{% endif %}">
<i class="fas fa-chart-line"></i> Events
</a></li>
Expand All @@ -47,6 +50,7 @@
<li><a href="/backoffice" class="{% if request.url.path == '/backoffice' %}active{% endif %}">Dashboard</a></li>
<li><a href="/backoffice/users" class="{% if '/users' in request.url.path %}active{% endif %}">Users</a></li>
<li><a href="/backoffice/api-keys" class="{% if '/api-keys' in request.url.path %}active{% endif %}">API Keys</a></li>
<li><a href="/backoffice/packages" class="{% if '/packages' in request.url.path %}active{% endif %}">Packages</a></li>
<li><a href="/backoffice/events" class="{% if '/events' in request.url.path %}active{% endif %}">Events</a></li>
</ul>
</div>
Expand Down
57 changes: 57 additions & 0 deletions app/src/templates/backoffice/packages.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{% extends "backoffice/layout.html" %}

{% block page_title %}Packages{% endblock %}

{% block backoffice_content %}
<div class="mb-8">
<h1 class="text-3xl font-bold text-base-content">Packages</h1>
<p class="text-base-content/70 mt-2">All tracked packages with analytics</p>
</div>

<div class="stats shadow mb-8 stats-vertical sm:stats-horizontal">
<div class="stat">
<div class="stat-title">Total Packages</div>
<div class="stat-value">{{ total_packages }}</div>
<div class="stat-figure text-primary">
<i class="fas fa-box text-2xl"></i>
</div>
</div>
<div class="stat">
<div class="stat-title">Total Events</div>
<div class="stat-value">{{ total_events }}</div>
<div class="stat-figure text-secondary">
<i class="fas fa-chart-line text-2xl"></i>
</div>
</div>
</div>

<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Package Name</th>
<th>Nr Events</th>
<th>Last Ingested</th>
</tr>
</thead>
<tbody>
{% for package in packages %}
<tr class="hover">
<td>
<span class="badge badge-secondary">{{ package.package_name }}</span>
</td>
<td class="font-mono">{{ package.nr_events }}</td>
<td class="font-mono text-sm">{{ package.last_ingested.strftime('%Y-%m-%d %H:%M') if package.last_ingested else '-' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

{% if not packages %}
<div class="text-center py-12 text-base-content/60">
<i class="fas fa-box text-5xl mb-4 opacity-50"></i>
<p class="text-lg">No packages tracked yet.</p>
</div>
{% endif %}
{% endblock %}