feat(ui): Live Changes (CDC) tab — real-time Debezium stream with filters, volume bars and before/after diff

This commit is contained in:
mo
2026-06-27 01:44:26 +02:00
parent 1a454f76cf
commit 921342442f
6 changed files with 299 additions and 3 deletions
+37
View File
@@ -2,6 +2,8 @@ import type {
PresentationData,
Agent,
Approval,
CdcChange,
CdcStats,
FeedEntry,
GpuStatus,
StatusData,
@@ -92,6 +94,41 @@ export async function fetchPresentation(): Promise<PresentationData | null> {
return fetchJson<PresentationData>('/api/presentation', 60000)
}
export async function fetchChanges(opts: { source?: string; op?: string; limit?: number } = {}) {
const p = new URLSearchParams()
if (opts.source) p.set('source', opts.source)
if (opts.op) p.set('op', opts.op)
p.set('limit', String(opts.limit ?? 150))
const j = await fetchJson<{ changes?: CdcChange[]; connected?: boolean; consumed?: number }>(
`/api/changes?${p.toString()}`, 8000,
)
return { changes: j?.changes || [], connected: !!j?.connected, consumed: j?.consumed || 0 }
}
export async function fetchChangeStats(minutes = 15): Promise<CdcStats | null> {
return fetchJson<CdcStats>(`/api/changes/stats?minutes=${minutes}`, 8000)
}
export async function fetchAgentOpsStatus() {
return fetchJson<Record<string, unknown>>('/api/agent-ops/status', 8000)
}
export function toggleAgentOps(enabled?: boolean) {
return fetch('/api/agent-ops/toggle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(enabled === undefined ? {} : { enabled }),
})
}
export function runAgentOpOnce(source?: string, op?: string) {
return fetch('/api/agent-ops/run-once', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source, op }),
})
}
export async function decideApproval(id: string, approved: boolean, decidedBy: string, note: string) {
return fetch(`/api/approvals/${id}/decide`, {
method: 'POST',