feat(events): timeline d'événements machine (tâche 5 backlog)
- listMachineEvents (machine_events, 30 derniers, desc) + route GET /machines/:id/events - api machineEvents ; section repliable « Timeline » dans le panneau détail (badge sévérité + horodatage), exploite les events déjà enregistrés par recordEvent tsc 0 · 118 tests · build OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
type DockerSettingsView,
|
||||
type DockerStackRow,
|
||||
type ImportantMessageView,
|
||||
type MachineEventView,
|
||||
type MachineHardwareView,
|
||||
type ProbeResultView,
|
||||
type ProfileManifestView,
|
||||
@@ -835,6 +836,26 @@ function fmtBytes(b: number | null): string {
|
||||
return `${b} o`;
|
||||
}
|
||||
|
||||
function TimelineSection({ machineId }: { machineId: string }) {
|
||||
const [events, setEvents] = useState<MachineEventView[] | null>(null);
|
||||
useEffect(() => {
|
||||
void api.machineEvents(machineId).then(setEvents).catch(() => setEvents([]));
|
||||
}, [machineId]);
|
||||
if (events === null) return <div className="machine-section-body"><span className="machine-placeholder">Chargement…</span></div>;
|
||||
if (events.length === 0) return <div className="machine-section-body"><span className="machine-placeholder">Aucun événement.</span></div>;
|
||||
return (
|
||||
<div className="machine-section-body">
|
||||
{events.map((e) => (
|
||||
<div key={e.id} className="docker-service">
|
||||
<DockerBadge status={e.severity === "error" ? "error" : e.severity === "warning" ? "updates_available" : "candidate"} />
|
||||
<span className="docker-service-name">{e.message ?? e.eventType}</span>
|
||||
<span className="docker-service-diff mono">{new Date(e.createdAt).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MessagesCard({ machineId }: { machineId: string }) {
|
||||
const [msgs, setMsgs] = useState<ImportantMessageView[]>([]);
|
||||
const load = () => { void api.machineMessages(machineId).then(setMsgs).catch(() => setMsgs([])); };
|
||||
@@ -1047,6 +1068,7 @@ export function MachineDetailPanel({
|
||||
const [dockerOpen, setDockerOpen] = useState(true);
|
||||
const [postOpen, setPostOpen] = useState(true);
|
||||
const [hwOpen, setHwOpen] = useState(true);
|
||||
const [tlOpen, setTlOpen] = useState(false);
|
||||
const [configOpen, setConfigOpen] = useState(false);
|
||||
const isError = machine.status === "error" || machine.status === "unknown";
|
||||
|
||||
@@ -1099,6 +1121,8 @@ export function MachineDetailPanel({
|
||||
{postOpen && <PostInstallSection machine={machine} onSelect={onSelect} />}
|
||||
<SectionToggle icon="cpu" title="Hardware" open={hwOpen} onToggle={() => setHwOpen((v) => !v)} />
|
||||
{hwOpen && <HardwareSection machineId={machine.id} />}
|
||||
<SectionToggle icon="clock" title="Timeline" open={tlOpen} onToggle={() => setTlOpen((v) => !v)} />
|
||||
{tlOpen && <TimelineSection machineId={machine.id} />}
|
||||
</div>
|
||||
|
||||
{configOpen && (
|
||||
|
||||
Reference in New Issue
Block a user