feat(app): real daemon health footer (live, uptime, version)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 12:09:35 +07:00
parent f7763a84fc
commit defceb1169
5 changed files with 53 additions and 12 deletions
+23 -7
View File
@@ -1,7 +1,16 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { Plus, ChevronDown, ChevronRight } from "lucide-react";
import { COLORS, FONT, STATE_COLOR } from "./theme";
import type { Group, WorkspaceView, SurfaceState } from "./layoutTypes";
import type { DaemonHealth } from "./socketBridge";
function fmtUptime(startedMs: number): string {
const s = Math.max(0, Math.floor((Date.now() - startedMs) / 1000));
if (s < 60) return `${s}s`;
if (s < 3600) return `${Math.floor(s / 60)}m`;
if (s < 86400) return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`;
return `${Math.floor(s / 86400)}d ${Math.floor((s % 86400) / 3600)}h`;
}
function aggregate(w: WorkspaceView): SurfaceState | "stopped" {
const order: SurfaceState[] = ["error", "wait", "work", "done", "idle"];
@@ -14,15 +23,22 @@ function aggregate(w: WorkspaceView): SurfaceState | "stopped" {
}
export function Sidebar({
groups, workspaces, activeId, onSelect, onNew,
groups, workspaces, activeId, onSelect, onNew, health, connected,
}: {
groups: Group[];
workspaces: WorkspaceView[];
activeId: string | null;
onSelect: (id: string) => void;
onNew: () => void;
health: DaemonHealth | null;
connected: boolean;
}) {
const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
const [, setTick] = useState(0);
useEffect(() => {
const t = setInterval(() => setTick((n) => n + 1), 30000);
return () => clearInterval(t);
}, []);
const byGroup = (gid: string | null) => workspaces.filter((w) => (w.group_id ?? null) === gid).sort((a, b) => a.order - b.order);
const ungrouped = byGroup(null);
@@ -76,12 +92,12 @@ export function Sidebar({
{ungrouped.length > 0 && <div style={{ marginTop: 4, display: "flex", flexDirection: "column", gap: 2 }}>{ungrouped.map(row)}</div>}
</div>
{/* Daemon status footer — uptime is mocked until the daemon reports it. */}
<div style={{ display: "flex", alignItems: "center", gap: 8, height: 30, marginTop: 10, padding: "0 6px", borderRadius: 6, background: COLORS.bgPanel }}>
<span style={{ width: 7, height: 7, borderRadius: "50%", background: COLORS.stDone, flex: "0 0 7px" }} />
<span style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.textSecondary }}>spaceshd · live</span>
<div title={health ? `spaceshd v${health.version} · pid ${health.pid}` : "daemon offline"}
style={{ display: "flex", alignItems: "center", gap: 8, height: 30, marginTop: 10, padding: "0 6px", borderRadius: 6, background: COLORS.bgPanel }}>
<span style={{ width: 7, height: 7, borderRadius: "50%", background: connected ? COLORS.stDone : COLORS.textMuted, flex: "0 0 7px" }} />
<span style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.textSecondary }}>{connected ? "spaceshd · live" : "spaceshd · offline"}</span>
<span style={{ flex: 1 }} />
<span style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.textMuted }}>3d 4h</span>
<span style={{ fontFamily: FONT.mono, fontSize: 11, color: COLORS.textMuted }}>{health ? fmtUptime(health.started_at_ms) : ""}</span>
</div>
</div>
);