feat(dashboard): vervang SECTIONS-grid door 5 live status-widgets
Parallel server-side fetches via Promise.allSettled voor Docker, Caddy, systemd, Git en Audit. Iedere widget toont geaggregeerde status en refresht elke 30s client-side onafhankelijk van de andere widgets. - lib/agent-fetch.ts: gedeelde client-side streaming helper - app/api/audit/latest/route.ts: GET endpoint voor AuditWidget refresh - app/_components/DockerWidget.tsx: running/total containers - app/_components/CaddyWidget.tsx: soonest cert expiry in dagen - app/_components/SystemdWidget.tsx: healthy/total units (of niet geconfigureerd) - app/_components/GitWidget.tsx: dirty repo count (of niet geconfigureerd) - app/_components/AuditWidget.tsx: laatste FlowRun status + relatief tijdstip - app/page.tsx: vervangt SECTIONS-grid, doet parallel fetches, rendert widgets Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ae63876f21
commit
faa1463cd7
8 changed files with 523 additions and 22 deletions
41
lib/agent-fetch.ts
Normal file
41
lib/agent-fetch.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { apiFetch } from '@/lib/csrf'
|
||||
|
||||
export async function fetchAgentOutput(commandKey: string, args: string[] = []): Promise<string> {
|
||||
const res = await apiFetch('/api/agent/exec', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ command_key: commandKey, args }),
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text()
|
||||
throw new Error(`agent ${res.status}: ${text}`)
|
||||
}
|
||||
|
||||
const reader = res.body?.getReader()
|
||||
if (!reader) throw new Error('no response body')
|
||||
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ''
|
||||
let output = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
const lines = buffer.split('\n')
|
||||
buffer = lines.pop() ?? ''
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data:')) {
|
||||
try {
|
||||
const parsed = JSON.parse(line.slice(5).trim()) as { data?: string }
|
||||
if (parsed.data !== undefined) output += parsed.data
|
||||
} catch {
|
||||
// ignore malformed SSE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue