SPB Git

spb/autoscholar

Public
5.9 KB · 149 lines tsx
1import { listRuns, listWorkers, overviewCounts } from "@autoscholar/core";2import { papers, securityEvents } from "@autoscholar/db";3import { desc, sql } from "drizzle-orm";4import Link from "next/link";5import { StatusBadge } from "@/components/StatusBadge";6import { ago, fmtDateTime, fmtUsd } from "@/lib/format";7import { db } from "@/lib/server/db";89export const dynamic = "force-dynamic";1011export default async function Overview() {12  const d = db();13  const counts = await overviewCounts(d);14  const workers = await listWorkers(d);15  const [pubs] = await d.select({ n: sql<number>`count(*)::int` }).from(papers);16  const alerts = await d.select().from(securityEvents).orderBy(desc(securityEvents.createdAt)).limit(8);17  const recent = await listRuns(d, { limit: 8 });18  const tiles: Array<[string, number]> = [19    ["Running", (counts.byStatus.running ?? 0) + (counts.byStatus.provisioning ?? 0)],20    ["Queued", (counts.byStatus.queued ?? 0) + (counts.byStatus.retrying ?? 0)],21    ["Blocked", (counts.byStatus.quality_failed ?? 0) + (counts.byStatus.awaiting_approval ?? 0)],22    ["Completed", counts.byStatus.published ?? 0],23    ["Failed", (counts.byStatus.failed ?? 0) + (counts.byStatus.cancelled ?? 0)],24  ];25  const online = workers.filter((w) => w.status === "online" || w.status === "draining");26  return (27    <div className="space-y-8">28      <header className="flex flex-wrap items-end justify-between gap-3">29        <div>30          <p className="eyebrow">Command center</p>31          <h1 className="serif mt-1 text-2xl font-semibold">Overview</h1>32        </div>33        <Link href="/admin/new" className="btn btn-primary">34          New research35        </Link>36      </header>37      <section aria-label="Job counts" className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6">38        {tiles.map(([l, n]) => (39          <div key={l} className="surface p-4">40            <p className="label">{l}</p>41            <p className="serif mt-1 text-2xl">{n}</p>42          </div>43        ))}44        <div className="surface p-4">45          <p className="label">Provider spend</p>46          <p className="serif mt-1 text-2xl">{fmtUsd(counts.totalCostUsd)}</p>47          <p className="subtle text-xs">client-side estimate</p>48        </div>49      </section>50      <section className="grid gap-6 lg:grid-cols-2">51        <div className="surface p-4">52          <div className="flex items-center justify-between">53            <h2 className="serif text-lg font-semibold">MacLustr workers</h2>54            <Link href="/admin/maclustr" className="text-xs">55              Details56            </Link>57          </div>58          <p className="muted text-xs">59            {online.length} of {workers.length} online · {pubs?.n ?? 0} publications60          </p>61          <ul className="mt-3 space-y-2 text-sm">62            {workers.length === 0 && (63              <li className="muted">64                No worker has enrolled yet. Install the worker daemon on a MacLustr node (docs/OPERATIONS.md).65              </li>66            )}67            {workers.map((w) => (68              <li key={w.id} className="flex items-center justify-between gap-2">69                <span className="font-mono">{w.name}</span>70                <span className="flex items-center gap-2">71                  <StatusBadge status={w.status} />72                  <span className="subtle text-xs">73                    {w.running}/{w.capacity} · {ago(w.lastHeartbeatAt)}74                  </span>75                </span>76              </li>77            ))}78          </ul>79        </div>80        <div className="surface p-4">81          <div className="flex items-center justify-between">82            <h2 className="serif text-lg font-semibold">Security alerts</h2>83            <Link href="/admin/settings" className="text-xs">84              Audit log85            </Link>86          </div>87          <ul className="mt-3 space-y-1 text-xs">88            {alerts.length === 0 && <li className="muted">No security events recorded.</li>}89            {alerts.map((a) => (90              <li key={a.id} className="flex justify-between gap-2">91                <span>92                  <span className="font-mono">{a.kind}</span>{" "}93                  <span className="muted">{JSON.stringify(a.detail).slice(0, 80)}</span>94                </span>95                <span className="subtle whitespace-nowrap">{fmtDateTime(a.createdAt)}</span>96              </li>97            ))}98          </ul>99        </div>100      </section>101      <section className="surface p-4">102        <div className="flex items-center justify-between">103          <h2 className="serif text-lg font-semibold">Recent runs</h2>104          <Link href="/admin/runs" className="text-xs">105            All runs106          </Link>107        </div>108        <table className="table mt-3">109          <thead>110            <tr>111              <th>Title</th>112              <th>Status</th>113              <th>Stage</th>114              <th>Attempt</th>115              <th>Cost</th>116              <th>Updated</th>117            </tr>118          </thead>119          <tbody>120            {recent.length === 0 && (121              <tr>122                <td colSpan={6} className="muted py-6 text-center">123                  No runs yet. Upload a research instruction to start.124                </td>125              </tr>126            )}127            {recent.map((r) => (128              <tr key={r.id}>129                <td>130                  <Link href={`/admin/runs/${r.id}`}>{r.title}</Link>131                </td>132                <td>133                  <StatusBadge status={r.status} />134                </td>135                <td className="font-mono text-xs">{r.currentStage ?? "—"}</td>136                <td>137                  {r.currentAttemptNo}/{r.maxAttempts}138                </td>139                <td>{fmtUsd(r.totalCostUsd)}</td>140                <td className="subtle text-xs">{ago(r.updatedAt)}</td>141              </tr>142            ))}143          </tbody>144        </table>145      </section>146    </div>147  );148}149
Files · 92ac91ccf92462b603d014c0325ba84f64d10ea5
Menu

Theme