Extract dashboard simple route table (#70)
* review: harden readonly git checks and lint verification * test: satisfy readonly reviewer sandbox typing * test: fix readonly reviewer sandbox assertions * test: remove impossible readonly sandbox guards * test: relax readonly sandbox assertions * test: cast readonly sandbox expectation shape * test: cast readonly sandbox expectation through unknown * Phase 0 — STEP_DONE/TASK_DONE split + owner-follow-up integration smoke * Add STEP_DONE guards, verdict storage, and stale delivery suppression * style: format paired stepdone telemetry files * Route STEP_DONE through reviewer * Add structured Discord attachments * style: format structured attachment test * Fix room thread bot output parity * Remove duplicate work item attachment migration from owner branch * Remove legacy dashboard rooms renderer * Extract dashboard parsed body renderer * Extract dashboard redaction helpers * Extract dashboard RoomCardV2 component * Include RoomCardV2 test in vitest suite * Extract dashboard RoomBoardV2 component * Extract dashboard EmptyState component * Extract dashboard InboxPanel component * Split dashboard InboxPanel card renderer * Extract dashboard TaskPanel component * Extract dashboard UsagePanel component * Fix dashboard room thread chunk rendering * Extract dashboard ServicePanel component * Render dashboard live progress markdown * Extract dashboard SettingsPanel component * Fix structured attachment rendering * Extract dashboard simple route table
This commit is contained in:
100
src/web-dashboard-routes.test.ts
Normal file
100
src/web-dashboard-routes.test.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import type { StatusSnapshot } from './status-dashboard.js';
|
||||||
|
import type { ScheduledTask } from './types.js';
|
||||||
|
import { handleSimpleGetRoute } from './web-dashboard-routes.js';
|
||||||
|
|
||||||
|
function jsonResponse(value: unknown, init?: ResponseInit): Response {
|
||||||
|
return new Response(JSON.stringify(value), {
|
||||||
|
...init,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json; charset=utf-8',
|
||||||
|
...(init?.headers as Record<string, string> | undefined),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeTask(overrides: Partial<ScheduledTask> = {}): ScheduledTask {
|
||||||
|
return {
|
||||||
|
id: 'task-1',
|
||||||
|
group_folder: 'general',
|
||||||
|
chat_jid: 'dc:general',
|
||||||
|
agent_type: null,
|
||||||
|
status_message_id: null,
|
||||||
|
status_started_at: null,
|
||||||
|
prompt: 'regular scheduled task',
|
||||||
|
schedule_type: 'cron',
|
||||||
|
schedule_value: '* * * * *',
|
||||||
|
context_mode: 'group',
|
||||||
|
next_run: '2026-04-26T05:00:00.000Z',
|
||||||
|
last_run: null,
|
||||||
|
last_result: null,
|
||||||
|
status: 'active',
|
||||||
|
created_at: '2026-04-26T04:00:00.000Z',
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('web dashboard simple routes', () => {
|
||||||
|
const snapshots: StatusSnapshot[] = [
|
||||||
|
{
|
||||||
|
serviceId: 'codex-main',
|
||||||
|
agentType: 'codex',
|
||||||
|
assistantName: 'Codex',
|
||||||
|
updatedAt: '2026-04-26T05:00:00.000Z',
|
||||||
|
entries: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it('serves health, snapshots, and task JSON routes', async () => {
|
||||||
|
const routeContext = {
|
||||||
|
statusMaxAgeMs: 1234,
|
||||||
|
readSnapshots: (maxAgeMs: number) => {
|
||||||
|
expect(maxAgeMs).toBe(1234);
|
||||||
|
return snapshots;
|
||||||
|
},
|
||||||
|
loadTasks: () => [makeTask()],
|
||||||
|
jsonResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
const health = handleSimpleGetRoute({
|
||||||
|
...routeContext,
|
||||||
|
url: new URL('http://localhost/api/health'),
|
||||||
|
});
|
||||||
|
expect(health?.status).toBe(200);
|
||||||
|
await expect(health?.json()).resolves.toEqual({ ok: true });
|
||||||
|
|
||||||
|
const status = handleSimpleGetRoute({
|
||||||
|
...routeContext,
|
||||||
|
url: new URL('http://localhost/api/status-snapshots'),
|
||||||
|
});
|
||||||
|
expect(status?.status).toBe(200);
|
||||||
|
await expect(status?.json()).resolves.toEqual(snapshots);
|
||||||
|
|
||||||
|
const tasks = handleSimpleGetRoute({
|
||||||
|
...routeContext,
|
||||||
|
url: new URL('http://localhost/api/tasks'),
|
||||||
|
});
|
||||||
|
expect(tasks?.status).toBe(200);
|
||||||
|
await expect(tasks?.json()).resolves.toMatchObject([
|
||||||
|
{
|
||||||
|
id: 'task-1',
|
||||||
|
groupFolder: 'general',
|
||||||
|
promptPreview: 'regular scheduled task',
|
||||||
|
status: 'active',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null for routes outside the simple table', () => {
|
||||||
|
const result = handleSimpleGetRoute({
|
||||||
|
url: new URL('http://localhost/api/overview'),
|
||||||
|
statusMaxAgeMs: 1234,
|
||||||
|
readSnapshots: () => [],
|
||||||
|
loadTasks: () => [],
|
||||||
|
jsonResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
35
src/web-dashboard-routes.ts
Normal file
35
src/web-dashboard-routes.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { StatusSnapshot } from './status-dashboard.js';
|
||||||
|
import type { ScheduledTask } from './types.js';
|
||||||
|
import { sanitizeScheduledTask } from './web-dashboard-data.js';
|
||||||
|
import { serveValidatedAttachment } from './web-dashboard-attachments.js';
|
||||||
|
|
||||||
|
type JsonResponse = (
|
||||||
|
value: unknown,
|
||||||
|
init?: ResponseInit,
|
||||||
|
request?: Request,
|
||||||
|
) => Response;
|
||||||
|
|
||||||
|
interface SimpleGetRouteContext {
|
||||||
|
url: URL;
|
||||||
|
statusMaxAgeMs: number;
|
||||||
|
readSnapshots: (maxAgeMs: number) => StatusSnapshot[];
|
||||||
|
loadTasks: () => ScheduledTask[];
|
||||||
|
jsonResponse: JsonResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleSimpleGetRoute({
|
||||||
|
url,
|
||||||
|
statusMaxAgeMs,
|
||||||
|
readSnapshots,
|
||||||
|
loadTasks,
|
||||||
|
jsonResponse,
|
||||||
|
}: SimpleGetRouteContext): Response | null {
|
||||||
|
const simpleGetRoutes: Record<string, () => Response> = {
|
||||||
|
'/api/health': () => jsonResponse({ ok: true }),
|
||||||
|
'/api/status-snapshots': () => jsonResponse(readSnapshots(statusMaxAgeMs)),
|
||||||
|
'/api/tasks': () => jsonResponse(loadTasks().map(sanitizeScheduledTask)),
|
||||||
|
'/api/attachments': () => serveValidatedAttachment(url),
|
||||||
|
};
|
||||||
|
const route = simpleGetRoutes[url.pathname];
|
||||||
|
return route ? route() : null;
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ import {
|
|||||||
buildWebDashboardOverview,
|
buildWebDashboardOverview,
|
||||||
sanitizeScheduledTask,
|
sanitizeScheduledTask,
|
||||||
} from './web-dashboard-data.js';
|
} from './web-dashboard-data.js';
|
||||||
import { serveValidatedAttachment } from './web-dashboard-attachments.js';
|
import { handleSimpleGetRoute } from './web-dashboard-routes.js';
|
||||||
import {
|
import {
|
||||||
addClaudeAccountFromToken,
|
addClaudeAccountFromToken,
|
||||||
getActiveCodexSettingsIndex,
|
getActiveCodexSettingsIndex,
|
||||||
@@ -1571,15 +1571,14 @@ export function createWebDashboardHandler(
|
|||||||
return jsonResponse({ error: 'Method not allowed' }, { status: 405 });
|
return jsonResponse({ error: 'Method not allowed' }, { status: 405 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const simpleGetRoutes: Record<string, () => Response> = {
|
const simpleGetRoute = handleSimpleGetRoute({
|
||||||
'/api/health': () => jsonResponse({ ok: true }),
|
url,
|
||||||
'/api/status-snapshots': () =>
|
statusMaxAgeMs,
|
||||||
jsonResponse(readSnapshots(statusMaxAgeMs)),
|
readSnapshots,
|
||||||
'/api/tasks': () => jsonResponse(loadTasks().map(sanitizeScheduledTask)),
|
loadTasks,
|
||||||
'/api/attachments': () => serveValidatedAttachment(url),
|
jsonResponse,
|
||||||
};
|
});
|
||||||
const simpleGetRoute = simpleGetRoutes[url.pathname];
|
if (simpleGetRoute) return simpleGetRoute;
|
||||||
if (simpleGetRoute) return simpleGetRoute();
|
|
||||||
|
|
||||||
if (url.pathname === '/api/overview') {
|
if (url.pathname === '/api/overview') {
|
||||||
const snapshots = readSnapshots(statusMaxAgeMs);
|
const snapshots = readSnapshots(statusMaxAgeMs);
|
||||||
|
|||||||
Reference in New Issue
Block a user