spb/autoscholar
Public
1import { listWorkers, workerTelemetry } from "@autoscholar/core";2import { StatusBadge } from "@/components/StatusBadge";3import { ago, fmtBytes, fmtDateTime } from "@/lib/format";4import { db } from "@/lib/server/db";5import { WorkerActions } from "./WorkerActions";67export const dynamic = "force-dynamic";89export default async function MacLustrPage() {10 const d = db();11 const workers = await listWorkers(d);12 const tele = await Promise.all(workers.map((w) => workerTelemetry(d, w.id, 20)));13 return (14 <div className="space-y-6">15 <header>16 <p className="eyebrow">Compute</p>17 <h1 className="title-2 mt-1">MacLustr workers</h1>18 <p className="muted mt-1 text-sm">19 Authenticated worker telemetry. Workers connect outbound over the private network; nothing here is20 exposed publicly.21 </p>22 </header>23 {workers.length === 0 && (24 <div className="surface p-8 text-center">25 <p className="serif text-lg">No worker enrolled.</p>26 <p className="muted mt-1 text-sm">27 Install the worker daemon on a MacLustr node with the enrollment secret (docs/OPERATIONS.md). It28 appears here after its first heartbeat.29 </p>30 </div>31 )}32 {workers.map((w, i) => {33 const nm = (w.nodeMeta ?? {}) as Record<string, unknown>;34 const hb = tele[i] ?? [];35 const last = hb[0]?.load as Record<string, number> | undefined;36 return (37 <section key={w.id} className="surface p-4" aria-labelledby={`w-${w.id}`}>38 <div className="flex flex-wrap items-start justify-between gap-3">39 <div>40 <h2 id={`w-${w.id}`} className="title-3">41 {w.name} <StatusBadge status={w.status} />42 </h2>43 <p className="muted text-xs">44 worker {w.version ?? "?"} · protocol {w.protocolVersion} · enrolled{" "}45 {fmtDateTime(w.enrolledAt)} · heartbeat {ago(w.lastHeartbeatAt)}46 {w.quarantineReason ? ` · quarantined: ${w.quarantineReason}` : ""}47 </p>48 </div>49 <WorkerActions id={w.id} status={w.status} draining={!!w.drainRequested} />50 </div>51 <dl className="mt-3 grid grid-cols-2 gap-x-6 gap-y-1 text-xs sm:grid-cols-4">52 <dt className="muted">Running / capacity</dt>53 <dd>54 {w.running} / {w.capacity}55 </dd>56 <dt className="muted">Machine</dt>57 <dd className="font-mono">58 {String(nm.arch ?? "?")} · {String(nm.cpuCores ?? "?")} cores ·{" "}59 {nm.memoryBytes ? fmtBytes(Number(nm.memoryBytes)) : "?"}60 </dd>61 <dt className="muted">OS / user</dt>62 <dd className="font-mono">63 {String(nm.osVersion ?? "?")} · {String(nm.user ?? "?")}64 </dd>65 <dt className="muted">Sandbox</dt>66 <dd>67 {nm.sandboxAvailable ? (68 <span style={{ color: "var(--color-ok-500)" }}>available (required for dispatch)</span>69 ) : (70 <span style={{ color: "var(--color-danger-500)" }}>unavailable — no jobs dispatched</span>71 )}72 </dd>73 <dt className="muted">Profiles</dt>74 <dd className="font-mono">{w.profiles.join(", ")}</dd>75 <dt className="muted">SDK</dt>76 <dd className="font-mono">{String(nm.sdkVersion ?? "?")}</dd>77 <dt className="muted">Load / free mem / free disk</dt>78 <dd className="font-mono">79 {last80 ? `${last.load1?.toFixed?.(2) ?? "?"} · ${last.memFreeBytes ? fmtBytes(last.memFreeBytes) : "?"} · ${last.diskFreeBytes ? fmtBytes(last.diskFreeBytes) : "?"}`81 : "—"}82 </dd>83 <dt className="muted">Tools</dt>84 <dd className="font-mono">85 {nm.tools && typeof nm.tools === "object"86 ? Object.entries(nm.tools as Record<string, string>)87 .map(([k, v]) => `${k} ${v}`)88 .join(" · ")89 : "—"}90 </dd>91 </dl>92 {hb.length > 1 && (93 <p className="subtle mt-2 text-xs">94 Last {hb.length} heartbeats:{" "}95 {hb96 .map((h) => `${(h.load as { load1?: number }).load1?.toFixed?.(1) ?? "?"}`)97 .reverse()98 .join(" ")}99 </p>100 )}101 </section>102 );103 })}104 </div>105 );106}107