dashboard: point frontend at the /api/* backend routes

Phase 2 of the dashboard route consolidation. The backend now double-registers
every bare top-level route (approve/deny/kill/restart/start/rebuild/destroy/
update-all/answer-question/cancel-question/purge-tombstone/matrix-account-login/
cancel-reminder/retry-reminder/request-spawn/op-send/meta-update/dashboard-stream/
dashboard-history) at an additional /api/<same> path. Switch every dashboard-pkg
fetch / EventSource / form action to the /api/ form so "backend = /api/*" holds
on the frontend side too.

Bare paths still answer, so this is independently deployable; once it ships the
backend can drop the bare registrations (phase 3). webhook/knowledge stays a
distinct webhook prefix (forge-driven, no SPA caller). app.js's /rebuild,/start
are the agent harness UI (different server) and are untouched.

Part of #1846 (phase 2).
This commit is contained in:
iris 2026-06-22 00:45:26 +02:00
commit 905b38b6c7
9 changed files with 36 additions and 36 deletions

View file

@ -364,16 +364,16 @@ window.marked = marked;
// Show only actions that are applicable in the current state.
if (c.running) {
dropdown.append(
menuItem('↺ R3ST4RT', { action: '/restart/', confirm: `restart ${c.name}?` }),
menuItem('■ ST0P', { action: '/kill/', confirm: `stop ${c.name}?`, confirmLabel: '■ stop', graceful: true }),
menuItem('↺ R3ST4RT', { action: '/api/restart/', confirm: `restart ${c.name}?` }),
menuItem('■ ST0P', { action: '/api/kill/', confirm: `stop ${c.name}?`, confirmLabel: '■ stop', graceful: true }),
);
} else {
dropdown.append(
menuItem('▶ ST4RT', { action: '/start/', confirm: `start ${c.name}?` }),
menuItem('▶ ST4RT', { action: '/api/start/', confirm: `start ${c.name}?` }),
);
}
dropdown.append(
menuItem('↻ R3BU1LD', { action: '/rebuild/', confirm: `rebuild ${c.name}? hot-reloads the container.` }),
menuItem('↻ R3BU1LD', { action: '/api/rebuild/', confirm: `rebuild ${c.name}? hot-reloads the container.` }),
menuSep(),
// Deep-link to the AGENT log tab pre-filtered to this container.
// The ?agent= param is read by logs.js on load and pre-selects this
@ -387,11 +387,11 @@ window.marked = marked;
dropdown.append(
menuSep(),
menuItem('DESTR0Y', {
action: '/destroy/',
action: '/api/destroy/',
confirm: `destroy ${c.name}? container removed; state + creds kept.`,
}),
menuItem('PURG3', {
action: '/destroy/',
action: '/api/destroy/',
body: { purge: 'on' },
confirm: `PURGE ${c.name}? WIPES container, config history, claude creds, and notes. no undo.`,
}),
@ -722,7 +722,7 @@ window.marked = marked;
}
if (c.needs_update) {
head.append(form(
'/rebuild/' + c.name, 'badge badge-warn btn-inline', 'needs update ↻',
'/api/rebuild/' + c.name, 'badge badge-warn btn-inline', 'needs update ↻',
'rebuild ' + c.name + '? hot-reloads the container.',
{}, { noRefresh: true },
));
@ -802,7 +802,7 @@ window.marked = marked;
if (anyStale) {
root.append(form(
'/update-all', 'btn-rebuild', '↻ UPD4TE 4LL',
'/api/update-all', 'btn-rebuild', '↻ UPD4TE 4LL',
'rebuild every stale container?',
{}, { noRefresh: true },
));
@ -1001,32 +1001,32 @@ window.marked = marked;
}
addBulkButton(actions, 'btn-restart', '↺ R3ST4RT', allRunning, selected, {
action: '/restart/',
action: '/api/restart/',
confirm: (names) => `restart ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`,
disabledTitle: why('↺ R3ST4RT', stoppedNames.map((n) => `\`${n}\` is stopped`)),
});
addBulkButton(actions, 'btn-stop', '■ ST0P', allRunning, selected, {
action: '/kill/',
action: '/api/kill/',
confirm: (names) => `stop ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`,
confirmLabel: '■ stop',
graceful: true,
disabledTitle: why('■ ST0P', stoppedNames.map((n) => `\`${n}\` is already stopped`)),
});
addBulkButton(actions, 'btn-start', '▶ ST4RT', allStopped, selected, {
action: '/start/',
action: '/api/start/',
confirm: (names) => `start ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`,
disabledTitle: why('▶ ST4RT', runningNames.map((n) => `\`${n}\` is already running`)),
});
addBulkButton(actions, 'btn-rebuild', '↻ R3BU1LD', true, selected, {
action: '/rebuild/',
action: '/api/rebuild/',
confirm: (names) => `rebuild ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? hot-reloads each container.`,
});
addBulkButton(actions, 'btn-destroy', 'DESTR0Y', true, selected, {
action: '/destroy/',
action: '/api/destroy/',
confirm: (names) => `destroy ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? containers are removed; state + creds kept.`,
});
addBulkButton(actions, 'btn-destroy', 'PURG3', true, selected, {
action: '/destroy/',
action: '/api/destroy/',
body: { purge: 'on' },
confirm: (names) => `PURGE ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? containers, config history, claude creds, and notes are all WIPED. no undo.`,
});
@ -1487,7 +1487,7 @@ window.marked = marked;
// lost during the disconnect window (same pattern as flow.js's
// onStreamOpen).
//
// Both /dashboard.html and /flow.html subscribe to `/dashboard/stream`
// Both /dashboard.html and /flow.html subscribe to `/api/dashboard/stream`
// and filter client-side — the dashboard ignores broker traffic
// and the inbox ignores mutation events.
const MUTATION_HANDLERS = {
@ -1514,7 +1514,7 @@ window.marked = marked;
// paragraph) for the design + Firefox throttling motivation.
// `openStream` returns an EventSource-shaped facade with a graceful
// direct-EventSource fallback when SharedWorker isn't supported.
const es = openStream('/dashboard/stream');
const es = openStream('/api/dashboard/stream');
es.onmessage = (e) => {
let ev;
try { ev = JSON.parse(e.data); } catch { return; }