feat(probe): sonde enrichie CPU/RAM/disques + recommandations de profils (tâche 4)
- template machine-probe : lscpu Model name + nproc, MemTotal, lsblk disques - parseProbe étendu (cpuModel/cpuCores/memoryBytes/disks) + buildRecommendations (KVM/QEMU → vm_guest_tools) ; tests TDD - runProbe persiste cpu/mem/disks dans machine_hardware ; /probe renvoie recommendations - popup Sonde affiche cpu/ram/disks + profils conseillés tsc 0 · 110 tests · build OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,10 @@ export interface ProbeResult {
|
||||
isRpi: boolean;
|
||||
gpus: string[];
|
||||
net: { iface: string; addr: string }[];
|
||||
cpuModel: string | null;
|
||||
cpuCores: number | null;
|
||||
memoryBytes: number | null;
|
||||
disks: { name: string; sizeBytes: number }[];
|
||||
}
|
||||
|
||||
export interface CorrectionProposal {
|
||||
@@ -30,6 +34,11 @@ export interface CorrectionProposal {
|
||||
virtualization: string;
|
||||
}
|
||||
|
||||
export interface ProfileRecommendation {
|
||||
profileId: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
function section(raw: string, start: string, end?: string): string {
|
||||
const i = raw.indexOf(start);
|
||||
if (i < 0) return "";
|
||||
@@ -51,7 +60,10 @@ export function parseProbe(raw: string): ProbeResult {
|
||||
const prox = section(raw, "===SU:PROBE_PROXMOX===", "===SU:PROBE_RPI===");
|
||||
const rpi = section(raw, "===SU:PROBE_RPI===", "===SU:PROBE_GPU===");
|
||||
const gpuBlock = section(raw, "===SU:PROBE_GPU===", "===SU:PROBE_NET===");
|
||||
const netBlock = section(raw, "===SU:PROBE_NET===", "===SU:EXIT=");
|
||||
const netBlock = section(raw, "===SU:PROBE_NET===", "===SU:PROBE_CPU===");
|
||||
const cpuBlock = section(raw, "===SU:PROBE_CPU===", "===SU:PROBE_MEM===");
|
||||
const memBlock = section(raw, "===SU:PROBE_MEM===", "===SU:PROBE_DISK===");
|
||||
const diskBlock = section(raw, "===SU:PROBE_DISK===", "===SU:EXIT=");
|
||||
|
||||
const gpus = gpuBlock
|
||||
.split("\n")
|
||||
@@ -66,6 +78,17 @@ export function parseProbe(raw: string): ProbeResult {
|
||||
}
|
||||
}
|
||||
|
||||
const cpuModelMatch = /^MODEL=(.+)$/m.exec(cpuBlock);
|
||||
const coresMatch = /^\s*(\d+)\s*$/m.exec(cpuBlock);
|
||||
const memMatch = /^MemTotal:\s+(\d+)\s*kB/m.exec(memBlock);
|
||||
|
||||
const disks: ProbeResult["disks"] = [];
|
||||
for (const line of diskBlock.split("\n")) {
|
||||
if (!line.startsWith("DISK\t")) continue;
|
||||
const [, name, size] = line.split("\t");
|
||||
if (name) disks.push({ name, sizeBytes: Number(size) || 0 });
|
||||
}
|
||||
|
||||
return {
|
||||
osId: osReleaseValue(os, "ID"),
|
||||
osVersion: osReleaseValue(os, "VERSION_ID"),
|
||||
@@ -77,9 +100,24 @@ export function parseProbe(raw: string): ProbeResult {
|
||||
isRpi: /RPI=1/.test(rpi),
|
||||
gpus,
|
||||
net,
|
||||
cpuModel: cpuModelMatch?.[1]?.trim() || null,
|
||||
cpuCores: coresMatch?.[1] ? Number(coresMatch[1]) : null,
|
||||
memoryBytes: memMatch?.[1] ? Number(memMatch[1]) * 1024 : null,
|
||||
disks,
|
||||
};
|
||||
}
|
||||
|
||||
/** Recommandations de profils post-install déduites de la sonde. */
|
||||
export function buildRecommendations(p: ProbeResult): ProfileRecommendation[] {
|
||||
const recs: ProfileRecommendation[] = [];
|
||||
if (p.virt === "kvm" || p.virt === "qemu") {
|
||||
recs.push({ profileId: "vm_guest_tools", reason: "QEMU/KVM détecté → qemu-guest-agent" });
|
||||
} else if (p.virt === "vmware") {
|
||||
recs.push({ profileId: "vm_guest_tools", reason: "VMware détecté → open-vm-tools" });
|
||||
}
|
||||
return recs;
|
||||
}
|
||||
|
||||
const VM_VIRTS = new Set(["kvm", "qemu", "vmware", "oracle", "microsoft", "xen", "bochs", "parallels"]);
|
||||
const LXC_VIRTS = new Set(["lxc", "lxc-libvirt", "openvz", "systemd-nspawn", "docker", "podman"]);
|
||||
|
||||
@@ -112,6 +150,7 @@ export function proposeCorrections(p: ProbeResult): CorrectionProposal {
|
||||
export interface ProbeOutcome {
|
||||
probe: ProbeResult;
|
||||
proposal: CorrectionProposal;
|
||||
recommendations: ProfileRecommendation[];
|
||||
raw: string;
|
||||
changes: string[]; // diff entre l'actuel et la proposition (pour l'UI)
|
||||
}
|
||||
@@ -129,17 +168,18 @@ export async function runProbe(machineId: string, onData?: (c: string) => void):
|
||||
const proposal = proposeCorrections(probe);
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const hwFields = {
|
||||
cpuModel: probe.cpuModel,
|
||||
cpuCores: probe.cpuCores,
|
||||
memoryBytes: probe.memoryBytes,
|
||||
disksJson: JSON.stringify(probe.disks),
|
||||
gpusJson: JSON.stringify(probe.gpus),
|
||||
networkJson: JSON.stringify(probe.net),
|
||||
updatedAt: now,
|
||||
};
|
||||
db.insert(schema.machineHardware)
|
||||
.values({
|
||||
machineId,
|
||||
gpusJson: JSON.stringify(probe.gpus),
|
||||
networkJson: JSON.stringify(probe.net),
|
||||
updatedAt: now,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: schema.machineHardware.machineId,
|
||||
set: { gpusJson: JSON.stringify(probe.gpus), networkJson: JSON.stringify(probe.net), updatedAt: now },
|
||||
})
|
||||
.values({ machineId, ...hwFields })
|
||||
.onConflictDoUpdate({ target: schema.machineHardware.machineId, set: hwFields })
|
||||
.run();
|
||||
|
||||
const changes: string[] = [];
|
||||
@@ -151,5 +191,5 @@ export async function runProbe(machineId: string, onData?: (c: string) => void):
|
||||
changes.push(`virtualization: ${m.virtualization ?? "—"} → ${proposal.virtualization}`);
|
||||
}
|
||||
|
||||
return { probe, proposal, raw, changes };
|
||||
return { probe, proposal, recommendations: buildRecommendations(probe), raw, changes };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user