feat(metrics): machine_metrics_simple — CPU/RAM/disque live par machine (tâche 4)
- template machine-metrics (loadavg/nproc, /proc/meminfo, df -B1) non destructif - parseMetrics (TDD) → cpu load/cores, mémoire kB→B + %, filesystems, warnings >=90% - collectMetrics (SSH léger) persiste machine_metrics_latest ; getLatestMetrics (sans SSH) - routes GET /machines/:id/metrics + POST /metrics/collect ; api latestMetrics/collectMetrics - section Hardware : bloc métriques live (CPU/RAM/disques + alertes) + bouton Collecter → comble le gap « Health » de la tâche 3 tsc 0 · 108 tests · build OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// client/src/features/machines/MachineTile.tsx
|
||||
import { useEffect, useState } from "react";
|
||||
import type { ActionType, AptProxyMode, MachineStatus, MachineView } from "@shared/types.js";
|
||||
import type { ActionType, AptProxyMode, MachineMetricsSimple, MachineStatus, MachineView } from "@shared/types.js";
|
||||
import { Button, Icon, IconButton, Popup, StatusLed } from "../../components/ui-kit.js";
|
||||
import {
|
||||
api,
|
||||
@@ -786,24 +786,67 @@ function PostInstallSection({ machine, onSelect }: { machine: MachineView; onSel
|
||||
);
|
||||
}
|
||||
|
||||
function fmtBytes(b: number | null): string {
|
||||
if (b === null) return "—";
|
||||
if (b >= 1e9) return `${(b / 1e9).toFixed(1)} Go`;
|
||||
if (b >= 1e6) return `${(b / 1e6).toFixed(0)} Mo`;
|
||||
return `${b} o`;
|
||||
}
|
||||
|
||||
function HardwareSection({ machineId }: { machineId: string }) {
|
||||
const [hw, setHw] = useState<MachineHardwareView | null>(null);
|
||||
const [metrics, setMetrics] = useState<MachineMetricsSimple | null>(null);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
try {
|
||||
setHw(await api.machineHardware(machineId));
|
||||
const [h, m] = await Promise.all([api.machineHardware(machineId), api.latestMetrics(machineId)]);
|
||||
setHw(h);
|
||||
setMetrics(m);
|
||||
} catch (e) {
|
||||
setErr((e as Error).message);
|
||||
}
|
||||
})();
|
||||
}, [machineId]);
|
||||
|
||||
const collect = async () => {
|
||||
setBusy(true);
|
||||
setErr(null);
|
||||
try {
|
||||
setMetrics(await api.collectMetrics(machineId));
|
||||
} catch (e) {
|
||||
setErr((e as Error).message);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (err) return <div className="machine-section-body"><span className="docker-msg docker-msg-err">{err}</span></div>;
|
||||
if (!hw) return <div className="machine-section-body"><span className="machine-placeholder">Chargement…</span></div>;
|
||||
|
||||
return (
|
||||
<div className="machine-section-body">
|
||||
<div className="machine-detail-card">
|
||||
<div className="cfg-block-head">
|
||||
<span className="label">Métriques (CPU / RAM / disque)</span>
|
||||
<Button icon="refresh" size="sm" onClick={busy ? undefined : collect}>{busy ? "…" : "Collecter"}</Button>
|
||||
</div>
|
||||
{metrics ? (
|
||||
<>
|
||||
<InfoRow k="CPU load" v={`${metrics.cpu.load1 ?? "—"} / ${metrics.cpu.cores ?? "?"}c`} mono />
|
||||
<InfoRow k="RAM" v={`${metrics.memory.usedPercent ?? "—"}% · ${fmtBytes(metrics.memory.usedBytes)} / ${fmtBytes(metrics.memory.totalBytes)}`} mono tone={(metrics.memory.usedPercent ?? 0) >= 90 ? "warn" : undefined} />
|
||||
{metrics.filesystems.map((fs) => (
|
||||
<InfoRow key={fs.mount} k={fs.mount} v={`${fs.usedPercent}% · ${fmtBytes(fs.usedBytes)} / ${fmtBytes(fs.sizeBytes)}`} mono tone={fs.usedPercent >= 90 ? "warn" : undefined} />
|
||||
))}
|
||||
{metrics.warnings.map((w, i) => <span key={i} className="docker-msg docker-msg-err">{w}</span>)}
|
||||
</>
|
||||
) : (
|
||||
<span className="machine-placeholder">Aucune métrique. Clique sur « Collecter ».</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="machine-detail-card">
|
||||
<InfoRow k="OS" v={`${hw.osFamily}${hw.osVersion ? ` ${hw.osVersion}` : ""}`} />
|
||||
<InfoRow k="Type" v={hw.machineKind ?? "—"} />
|
||||
|
||||
Reference in New Issue
Block a user