feat(messages): extraction des messages importants APT (tâche 5 backlog)
- extractImportantMessages (TDD) : E:/dpkg error → error, W:/GPG → warning, déprecations/EOL → future_major_change ; nettoyage des secrets dans les URLs - recordImportantMessages : dédup par (machine, source, message) non acquitté → maj lastSeenAt, sinon insert (firstSeen/lastSeen) dans important_messages - branché dans refreshMachine (sortie APT) avec snapshotId - routes GET /machines/:id/messages + POST .../:msgId/ack - UI : carte « Messages importants » (badge sévérité + ack) dans le panneau détail tsc 0 · 118 tests · build OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
api,
|
||||
type DockerSettingsView,
|
||||
type DockerStackRow,
|
||||
type ImportantMessageView,
|
||||
type MachineHardwareView,
|
||||
type ProbeResultView,
|
||||
type ProfileManifestView,
|
||||
@@ -834,6 +835,27 @@ function fmtBytes(b: number | null): string {
|
||||
return `${b} o`;
|
||||
}
|
||||
|
||||
function MessagesCard({ machineId }: { machineId: string }) {
|
||||
const [msgs, setMsgs] = useState<ImportantMessageView[]>([]);
|
||||
const load = () => { void api.machineMessages(machineId).then(setMsgs).catch(() => setMsgs([])); };
|
||||
useEffect(load, [machineId]);
|
||||
if (msgs.length === 0) return null;
|
||||
return (
|
||||
<div className="machine-detail-card">
|
||||
<span className="label">Messages importants ({msgs.length})</span>
|
||||
{msgs.slice(0, 8).map((m) => (
|
||||
<div key={m.id} className="docker-service">
|
||||
<DockerBadge status={m.severity === "error" ? "error" : m.severity === "warning" ? "updates_available" : "candidate"} />
|
||||
<span className="docker-service-name mono" title={m.message}>{m.message}</span>
|
||||
<button className="interactive su-viewtoggle-btn" style={{ padding: "2px 7px", fontSize: 11 }}
|
||||
onClick={() => api.ackMessage(machineId, m.id).then(load)}>ack</button>
|
||||
</div>
|
||||
))}
|
||||
{msgs.length > 8 && <span className="machine-placeholder">+{msgs.length - 8} autres…</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HardwareSection({ machineId }: { machineId: string }) {
|
||||
const [hw, setHw] = useState<MachineHardwareView | null>(null);
|
||||
const [metrics, setMetrics] = useState<MachineMetricsSimple | null>(null);
|
||||
@@ -1049,6 +1071,8 @@ export function MachineDetailPanel({
|
||||
<IconButton icon="cog" label="Profil & proxy (sonde)" active={false} danger={false} primary={false} onClick={() => setConfigOpen(true)} />
|
||||
</div>
|
||||
|
||||
<MessagesCard machineId={machine.id} />
|
||||
|
||||
<div className="machine-detail-cards">
|
||||
<div className="machine-detail-card">
|
||||
<span className="label">System info</span>
|
||||
|
||||
Reference in New Issue
Block a user