"use client"; import * as React from "react"; import { motion } from "framer-motion"; import { AlertTriangle, CheckCircle2 } from "lucide-react"; import { api, bytes, type Activity, type AdminUser, type SystemStats } from "@/lib/api"; import { StatCard } from "@/components/admin/stat-card"; import { ActivityChart } from "@/components/admin/activity-chart"; import { UsersTable } from "@/components/admin/users-table"; import { SkeletonCards } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; export function AdminPanel({ me }: { me: string | null }) { const [sys, setSys] = React.useState(null); const [act, setAct] = React.useState(null); const [users, setUsers] = React.useState<{ users: AdminUser[]; roles: string[] } | null>(null); const [err, setErr] = React.useState(null); const loadUsers = React.useCallback(() => { api.admin.users().then(setUsers).catch((e) => setErr((e as Error).message)); }, []); React.useEffect(() => { loadUsers(); api.admin.activity(30).then(setAct).catch(() => undefined); const poll = () => api.admin.system().then(setSys).catch(() => undefined); poll(); // 5s: CPU is a delta between samples, so the first reading is always null // and a slow poll would leave it null for an uncomfortably long time. const t = setInterval(poll, 5000); return () => clearInterval(t); }, [loadUsers]); if (err) return
{err}
; if (!sys || !users) return ; const s = sys.system; const lib = s.disks.library; const active = (sys.jobs.queued ?? 0) + (sys.jobs.searching ?? 0) + (sys.jobs.downloading ?? 0); return (
{act && } {act && act.per_user.length > 0 && (
({ label: r.u, n: r.n, sub: r.bytes ? bytes(r.bytes) : "", }))} /> ({ label: r.artist, n: r.n, sub: "" }))} />
)}
); } function SlskdBanner({ slskd }: { slskd: SystemStats["slskd"] }) { const healthy = slskd.ok && slskd.connected && slskd.logged_in; return ( {healthy ? ( ) : ( )} {healthy ? `slskd connected and logged in${slskd.version ? ` · v${slskd.version}` : ""}` : slskd.error ? `slskd unreachable — ${slskd.error}` : `slskd reachable but not logged in to Soulseek — nothing will download`} ); } function Breakdown({ title, rows, }: { title: string; rows: { label: string; n: number; sub: string }[]; }) { const max = Math.max(1, ...rows.map((r) => r.n)); return (

{title}

{rows.slice(0, 8).map((r, i) => ( {r.label}
{r.n} {r.sub && ( {r.sub} )}
))}
); }