import Link from 'next/link'; import { computeMetrics } from '@/lib/admin/metrics'; import { getSupabaseAdmin } from '@/lib/supabase/admin'; import { formatBytes, formatDate } from '@/lib/format'; export const dynamic = 'force-dynamic'; type OverQuotaRow = { user_id: string; subdomain: string; bytes_used: number; quota_bytes: number; }; export default async function AdminOverviewPage() { const metrics = await computeMetrics(); const admin = getSupabaseAdmin(); // Recent signups (latest 5 users). const { data: recentUsersData } = await admin.auth.admin.listUsers({ page: 1, perPage: 5, }); const recentUsers = recentUsersData?.users ?? []; // Over-quota tunnels (compute in memory). const { data: tunnelsData } = await admin .from('tunnels') .select('user_id, subdomain, bytes_used, quota_bytes'); const overQuota = ((tunnelsData ?? []) as OverQuotaRow[]) .filter((t) => t.quota_bytes > 0 && t.bytes_used >= t.quota_bytes) .slice(0, 5); const kpis: { label: string; value: string }[] = [ { label: 'Total users', value: String(metrics.totalUsers) }, { label: 'Total tunnels', value: String(metrics.totalTunnels) }, { label: 'Active tunnels', value: String(metrics.activeTunnels) }, { label: 'Inactive tunnels', value: String(metrics.inactiveTunnels) }, { label: 'Over quota', value: String(metrics.overQuota) }, { label: 'Active last 24h', value: String(metrics.recentlyActive) }, { label: 'Signups (7d)', value: String(metrics.signups7d) }, { label: 'Signups (30d)', value: String(metrics.signups30d) }, { label: 'Bandwidth used', value: formatBytes(metrics.bytesUsedTotal) }, { label: 'Total quota', value: formatBytes(metrics.quotaTotal) }, ]; return (
No users yet.
) : (| Joined | |
|---|---|
| {u.email ?? u.id} | {formatDate(u.created_at)} |
None over quota.
) : (| Subdomain | Usage |
|---|---|
| {t.subdomain} | {formatBytes(t.bytes_used)} / {formatBytes(t.quota_bytes)} |