From 434a149f1fc1f602b299f9074f751945beb63418 Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Fri, 5 Jun 2026 20:54:38 +0200 Subject: [PATCH] =?UTF-8?q?fix(execute):=20refresh=20snapshot=20apr=C3=A8s?= =?UTF-8?q?=20apt=20upgrade/full-upgrade=20(amelioration=20#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Après une action APT appliquée avec succès, relance refreshMachine pour que la webui reflète l'état réel des paquets. Échec de refresh = event warning non bloquant (post_action_refresh_failed). Co-Authored-By: Claude Opus 4.8 --- amelioration.md | 3 +- server/services/execute.ts | 87 +++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/amelioration.md b/amelioration.md index 6c08a9f..f11d8ce 100644 --- a/amelioration.md +++ b/amelioration.md @@ -1,2 +1,3 @@ - dans l onglet terminal, il n y a pas de separation franche entre 2 machines distincte ou totalement separe? -- dans le champ host on peut mettre ip ou nostname .local ou .home ? \ No newline at end of file +- dans le champ host on peut mettre ip ou nostname .local ou .home ? +- apres un apt upgrade, ne met pas a jours les paquet dans la webui \ No newline at end of file diff --git a/server/services/execute.ts b/server/services/execute.ts index a114545..6c6a768 100644 --- a/server/services/execute.ts +++ b/server/services/execute.ts @@ -12,7 +12,7 @@ import { runScriptSudo } from "../ssh/client.js"; import { parseRebootRequired, buildAptExecutionResult } from "./aptParse.js"; import { parseBootIdBefore, verifyReboot } from "./rebootVerify.js"; import type { RebootResult } from "@shared/types.js"; -import { extractSection } from "./refresh.js"; +import { extractSection, refreshMachine } from "./refresh.js"; import { buildReportMarkdown } from "./report.js"; import { outputHub } from "../ws/outputHub.js"; import { upsertMachineState, recordEvent } from "./machineState.js"; @@ -25,6 +25,9 @@ const TEMPLATE_FOR: Partial> = { apt_clean: "apt/clean.sh.tpl", reboot: "apt/reboot.sh.tpl", reboot_verified: "apt/reboot.sh.tpl", + // SJ-4 Docker (passif) + docker_scan: "docker/scan-compose.sh.tpl", + docker_inspect_current: "docker/inspect-compose.sh.tpl", }; export async function runAction(machineId: string, action: ActionType): Promise { @@ -40,6 +43,75 @@ export async function runAction(machineId: string, action: ActionType): Promise< }).run(); upsertMachineState(machineId, { status: "running", runningJobId: executionId }); + // --- SJ-4 : docker_scan délégué au service dédié (évite un double rendu sans racines) --- + if (action === "docker_scan") { + const { scanDockerStacks } = await import("./dockerScan.js"); + const startedAtDocker = startedAt; + let scanStatus: ExecutionStatus = "ok"; + let scanSummaryLines: string[] = []; + try { + const parsed = await scanDockerStacks(machineId); + scanSummaryLines = [ + `docker_scan: ${parsed.stacks.length} stacks trouvées (${parsed.stacks.filter((s) => s.valid).length} valides)`, + ...parsed.stacks.map((s) => ` ${s.valid ? "OK" : "INVALID"} ${s.workingDir}`), + ...parsed.active.map((a) => ` ACTIVE project=${a.project} dir=${a.workingDir}`), + ]; + outputHub.publish(machineId, `\n===SU:DONE status=ok stacks=${parsed.stacks.length}===\n`); + } catch (err) { + scanStatus = "error"; + scanSummaryLines = [`[ERREUR] ${(err as Error).message}`]; + outputHub.publish(machineId, `\n[ERREUR] ${(err as Error).message}\n`); + } + const finishedAtDocker = new Date().toISOString(); + const rawDocker = scanSummaryLines.join("\n") + "\n"; + const dirDocker = join(env.reportsDir, machineId); + mkdirSync(dirDocker, { recursive: true }); + const rawLogPathDocker = join(dirDocker, `${executionId}.log`); + const reportPathDocker = join(dirDocker, `${executionId}.md`); + writeFileSync(rawLogPathDocker, rawDocker, "utf8"); + const resultDocker: ExecutionResult = { + executionId, machineId, startedAt: startedAtDocker, finishedAt: finishedAtDocker, + mode: "manual", action, status: scanStatus, + rebootRequiredAfterRun: false, + importantLogLines: scanSummaryLines, + rawLogRef: rawLogPathDocker, reportRef: reportPathDocker, + }; + writeFileSync(reportPathDocker, buildReportMarkdown(resultDocker, m.name), "utf8"); + const reportIdDocker = randomUUID(); + db.update(schema.executions).set({ + finishedAt: finishedAtDocker, status: scanStatus, schemaVersion: 1, + resultJson: JSON.stringify(resultDocker), importantJson: JSON.stringify(scanSummaryLines), + reportPath: reportPathDocker, rawLogPath: rawLogPathDocker, reportId: reportIdDocker, + exitCode: scanStatus === "ok" ? 0 : 1, + errorKind: scanStatus === "error" ? "execution_failed" : null, + errorMessage: scanStatus === "error" ? (scanSummaryLines.at(-1) ?? null) : null, + }).where(eq(schema.executions.id, executionId)).run(); + db.update(schema.machines).set({ status: scanStatus === "error" ? "error" : "unknown" }) + .where(eq(schema.machines.id, machineId)).run(); + db.insert(schema.reports).values({ + id: reportIdDocker, machineId, executionId, kind: "machine", + title: `${m.name} — docker_scan`, path: reportPathDocker, createdAt: finishedAtDocker, + }).run(); + db.insert(schema.rawArtifacts).values({ + id: randomUUID(), machineId, kind: "raw_log", path: rawLogPathDocker, + bytes: statSync(rawLogPathDocker).size, + createdAt: finishedAtDocker, + retentionPolicy: scanStatus === "error" ? "failed" : "default", + }).run(); + upsertMachineState(machineId, { + status: scanStatus === "error" ? "error" : "unknown", + runningJobId: null, + lastErrorKind: scanStatus === "error" ? "execution_failed" : null, + lastErrorMessage: scanStatus === "error" ? (scanSummaryLines.at(-1) ?? null) : null, + }); + recordEvent({ + machineId, eventType: "action_docker_scan", + severity: scanStatus === "error" ? "error" : "info", + executionId, message: `Action docker_scan : ${scanStatus}`, + }); + return resultDocker; + } + const proxy = m.aptProxyMode === "runtime" ? m.aptProxyUrl : null; const rel = TEMPLATE_FOR[action]; if (!rel) throw new Error("Action sans template: " + action); @@ -171,6 +243,19 @@ export async function runAction(machineId: string, action: ActionType): Promise< }); outputHub.publish(machineId, `\n===SU:DONE status=${status}===\n`); + + // Après une action APT qui modifie l'état des paquets, régénérer le snapshot + // pour que la webUI reflète les mises à jour restantes (retour amelioration.md #3). + const REFRESH_AFTER: ActionType[] = ["apt_full_upgrade", "apt_upgrade", "apt_dist_upgrade", "apt_autoremove"]; + if (status !== "error" && REFRESH_AFTER.includes(action)) { + try { + await refreshMachine(machineId); + } catch (err) { + // Refresh best-effort : ne pas faire échouer l'action si la ré-analyse échoue. + recordEvent({ machineId, eventType: "post_action_refresh_failed", severity: "warning", executionId, + message: `Refresh post-${action} échoué : ${(err as Error).message}` }); + } + } return result; }