feat(ui): ajout machine OS/type, section Hardware, identité app (tâche 3)

- AddMachineModal : sélecteurs OS + Type machine ; createMachine accepte
  osFamily/machineKind (manuel prioritaire, "Autre/auto" → détection os-release)
- section Hardware sur la tuile + panneau détail : os/type/virt/arch/gpu/réseau
  depuis machine_hardware (sonde) via GET /machines/:id/hardware
- identité : favicon.svg (serveur + LED Gruvbox), favicon.ico, apple-touch-icon,
  PWA 192/512, site.webmanifest ; liens + theme-color dans index.html

tsc 0 · 104 tests · build OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 14:07:46 +02:00
parent 3b16fdd52a
commit 58abebf687
12 changed files with 167 additions and 4 deletions
+9
View File
@@ -2,6 +2,7 @@
import { Hono } from "hono";
import {
listMachines, createMachine, deleteMachine, updateMachine, getMachineRow, getCreds, testConnection,
getMachineHardware,
type CreateMachineInput, type UpdateMachineInput,
} from "../services/machines.js";
import { refreshMachine, getLatestSnapshot } from "../services/refresh.js";
@@ -53,6 +54,14 @@ machinesRoutes.patch("/:id", async (c) => {
}
});
machinesRoutes.get("/:id/hardware", (c) => {
try {
return c.json(getMachineHardware(c.req.param("id")));
} catch (err) {
return c.json({ error: (err as Error).message }, 404);
}
});
// Sonde synchrone (lecture seule) : renvoie faits + proposition de correction.
machinesRoutes.post("/:id/probe", async (c) => {
try {
+24 -2
View File
@@ -17,6 +17,8 @@ export interface CreateMachineInput {
sudoPassword?: string | null;
aptProxyMode?: AptProxyMode;
aptProxyUrl?: string | null;
osFamily?: OsFamily; // choix manuel ; sinon auto-détecté via os-release
machineKind?: MachineKind;
}
type MachineRow = typeof schema.machines.$inferSelect;
@@ -118,11 +120,11 @@ export async function createMachine(input: CreateMachineInput): Promise<MachineV
name: input.name,
hostname: input.hostname,
port: input.port,
osFamily: os.family,
osFamily: input.osFamily && input.osFamily !== "unknown" ? input.osFamily : os.family, // manuel prioritaire, "unknown" => auto
osVersion: os.version || null,
osCodename: null,
arch: null,
machineKind: null,
machineKind: input.machineKind ?? null,
virtualization: null,
hardwareProfile: null,
username: input.username,
@@ -146,6 +148,26 @@ export function deleteMachine(id: string): void {
db.delete(schema.machines).where(eq(schema.machines.id, id)).run();
}
/** Faits matériels d'une machine (machine_hardware rempli par machine_probe + colonnes machines). */
export function getMachineHardware(id: string) {
const m = getMachineRow(id);
if (!m) throw new Error("Machine introuvable");
const hw = db.select().from(schema.machineHardware).where(eq(schema.machineHardware.machineId, id)).get();
const parse = <T>(j: string | null | undefined): T[] => {
try { return j ? (JSON.parse(j) as T[]) : []; } catch { return []; }
};
return {
osFamily: m.osFamily,
osVersion: m.osVersion,
arch: m.arch,
machineKind: m.machineKind,
virtualization: m.virtualization,
gpus: parse<string>(hw?.gpusJson),
network: parse<{ iface: string; addr: string }>(hw?.networkJson),
probed: !!hw,
};
}
/** Applique un proxy APT à toutes les machines. Renvoie le nombre de machines modifiées. */
export function applyProxyToAllMachines(mode: AptProxyMode, url: string | null): number {
const res = db