Container events are Ham Lab's own record, not Docker's short memory
docker events is a live stream with a small in-memory backlog: ask for thirty days and you get a few minutes, and a daemon restart loses the lot. That is why Dockhand kept its own copy. A watcher now follows the stream and appends lifecycle events to a ring beside the config, flushed every thirty seconds, so the question of what happened to a container last week has an answer. Health-check exec events are excluded — three per probe, thousands a day, and the health panel says it better. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b3e1c495f0
commit
a70f3e2ed2
+6
-5
@@ -180,10 +180,11 @@ function Health({ id }: { id: string }) {
|
||||
// What has happened to this container lately, asked of Docker rather than kept here.
|
||||
function Events({ id }: { id: string }) {
|
||||
const [ev, setEv] = useState<DockerEvent[] | null>(null)
|
||||
const [kept, setKept] = useState(0)
|
||||
const [since, setSince] = useState('7d')
|
||||
const [err, setErr] = useState('')
|
||||
useEffect(() => { setEv(null); setErr(''); api.dockerEvents(id, since).then((r) => setEv(r.events)).catch((e) => setErr(e instanceof Error ? e.message : 'failed')) }, [id, since])
|
||||
const bad = (a: string) => /die|kill|oom|destroy|health_status: unhealthy/.test(a)
|
||||
useEffect(() => { setEv(null); setErr(''); api.dockerEvents(id, since).then((r) => { setEv(r.events); setKept(r.kept) }).catch((e) => setErr(e instanceof Error ? e.message : 'failed')) }, [id, since])
|
||||
const bad = (a: string, h?: string) => /die|kill|oom|destroy/.test(a) || (h !== undefined && h !== 'healthy')
|
||||
return (
|
||||
<>
|
||||
<div className="chips" style={{ marginBottom: 8 }}>
|
||||
@@ -191,14 +192,14 @@ function Events({ id }: { id: string }) {
|
||||
</div>
|
||||
{err && <div className="error">{err}</div>}
|
||||
{!ev && !err && <Loading label="Asking Docker…" />}
|
||||
{ev && ev.length === 0 && <div className="notice">Nothing in the last {since}. Docker keeps events only while the daemon has been up.</div>}
|
||||
{ev && ev.length === 0 && <div className="notice">Nothing for this container in the last {since}. Ham Lab keeps its own record — Docker's own only reaches back a few minutes — so history starts from when this version was installed. {kept} events kept across every container.</div>}
|
||||
{ev && ev.length > 0 && (
|
||||
<div className="snap-list">
|
||||
{ev.map((e, i) => (
|
||||
<div className="snap" key={i}>
|
||||
<span className={bad(e.action) ? 'warn-text' : ''}>{e.action}</span>
|
||||
<span className={bad(e.action, e.health) ? 'warn-text' : ''}>{e.action}{e.health ? `: ${e.health}` : ''}</span>
|
||||
<span className="muted">{new Date(e.t).toLocaleString()}</span>
|
||||
<span className="muted small">{e.exitCode !== undefined ? `exit ${e.exitCode}` : ''}{e.signal ? ` signal ${e.signal}` : ''}</span>
|
||||
<span className="muted small">{e.exit_code ? `exit ${e.exit_code}` : ''}{e.signal ? ` signal ${e.signal}` : ''}</span>
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -111,7 +111,7 @@ export interface SmartInfo { device: string; available: boolean; supported: bool
|
||||
export interface DockerContainer { id: string; name: string; image: string; state: string; status: string; health: string; ports: string; stack: string; service: string; created: string; mem: number }
|
||||
export interface DockerHealthProbe { Start: string; End: string; ExitCode: number; Output: string }
|
||||
export interface DockerHealth { configured: boolean; state: string; status?: string; failing_streak?: number; log?: DockerHealthProbe[] }
|
||||
export interface DockerEvent { action: string; t: number; exitCode?: string; signal?: string; name?: string; image?: string }
|
||||
export interface DockerEvent { t: number; name: string; action: string; image?: string; exit_code?: string; signal?: string; health?: string }
|
||||
|
||||
export interface DockerInfo { available: boolean; message?: string; tool?: string; containers: DockerContainer[]; images: Record<string, unknown>[]; volumes: Record<string, unknown>[]; networks: Record<string, unknown>[] }
|
||||
export interface Stack { name: string; containers: string[]; running: number; file: string; host_file: string; files: string[]; host_files: string[] | null; images: string[] | null; via: string; remote: string; held_by: string; editable: boolean; note?: string; has_env: boolean }
|
||||
@@ -253,7 +253,7 @@ export const api = {
|
||||
dockerLogs: (id: string, since = '') => req<{ lines: { t: string; m: string }[] }>('GET', `/api/docker/containers/${encodeURIComponent(id)}/logs${since ? `?since=${encodeURIComponent(since)}` : '?n=300'}`),
|
||||
dockerInspect: (id: string) => req<Record<string, unknown>>('GET', `/api/docker/containers/${encodeURIComponent(id)}/inspect`),
|
||||
dockerHealth: (id: string) => req<DockerHealth>('GET', `/api/docker/containers/${encodeURIComponent(id)}/health`),
|
||||
dockerEvents: (id: string, since = '7d') => req<{ events: DockerEvent[]; since: string }>('GET', `/api/docker/containers/${encodeURIComponent(id)}/events?since=${since}`),
|
||||
dockerEvents: (id: string, since = '7d') => req<{ events: DockerEvent[]; since: string; kept: number; capacity: number }>('GET', `/api/docker/containers/${encodeURIComponent(id)}/events?since=${since}`),
|
||||
dockerExec: (id: string) => req<{ id: string; name: string }>('POST', `/api/docker/containers/${encodeURIComponent(id)}/exec`, {}),
|
||||
dockerLimits: (id: string, p: { memory?: string; cpus?: string; restart?: string }) => req<void>('POST', `/api/docker/containers/${encodeURIComponent(id)}/limits`, p),
|
||||
stacks: () => req<{ stacks: Stack[] }>('GET', '/api/docker/stacks'),
|
||||
|
||||
Reference in New Issue
Block a user