mirror of
https://github.com/0xWheatyz/handler.git
synced 2026-08-30 19:06:25 +00:00
Replace Alpine frontend with Next.js Claude Activity Dashboard (#7)
Rebuild the bundled web UI as a Next.js (React + TypeScript) static export implementing the Claude Activity Dashboard design: a left-nav "Control Center" hub over Runs, Repositories, Agents, Approvals, Git Servers, Activity, and Shared, styled with the Leeworks design-system tokens (flat, dark, border-led). The dashboard is a pure client of the existing API (same contract as curl): the browser prompts for the token once, stores it in localStorage, attaches it to every call, and renders all API values as React text so agent-authored strings stay inert. Control actions enqueue a command and poll it to a terminal state, matching the worker model. The build output is committed to src/handler/api/static/ so the wheel ships it and FastAPI serves it same-origin. app.py now mounts the export at "/" after the API routers (a non-shadowing fallback: unmatched paths 404, no SPA rewrite). UI-serving tests updated for the export; frontend source lives in frontend/. Claude-Session: https://claude.ai/code/session_01ATgVWRjFzG8nHEnwgZpJWD Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/* Activity — the control-command queue: every enqueued action and its status
|
||||
* (queued → running → done/failed). The audit log of what the dashboard triggered. */
|
||||
"use client";
|
||||
|
||||
import { useDashboard } from "@/components/store";
|
||||
import { Button, StatusBadge } from "@/components/ui";
|
||||
import { fmtFull } from "@/lib/format";
|
||||
|
||||
export function ActivitySection() {
|
||||
const s = useDashboard();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="section-head">
|
||||
<div className="hstack" style={{ justifyContent: "space-between" }}>
|
||||
<div>
|
||||
<div className="section-title">Activity</div>
|
||||
<div className="section-desc">Control commands the worker drains from the queue.</div>
|
||||
</div>
|
||||
<Button variant="secondary" disabled={s.cmd.busy} onClick={() => s.pollCi()}>
|
||||
Sweep CI now
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section-body">
|
||||
{s.commands.length === 0 ? (
|
||||
<div className="empty">No commands yet.</div>
|
||||
) : (
|
||||
<div className="table-wrap">
|
||||
<table className="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>When</th>
|
||||
<th>Type</th>
|
||||
<th>Repository</th>
|
||||
<th>Agent</th>
|
||||
<th>Status</th>
|
||||
<th>Result / Error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{s.commands.map((c) => (
|
||||
<tr key={c.id}>
|
||||
<td className="faint nowrap">{fmtFull(c.created_at)}</td>
|
||||
<td className="mono">{c.type}</td>
|
||||
<td className="mono">{c.project_id || "—"}</td>
|
||||
<td className="mono">{c.agent_name || "—"}</td>
|
||||
<td>
|
||||
<StatusBadge status={c.status} />
|
||||
</td>
|
||||
<td className="mono faint" style={{ fontSize: "var(--text-xs)" }}>
|
||||
{c.error || (c.result ? JSON.stringify(c.result) : "—")}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user