import { readFile } from "node:fs/promises" import { join } from "node:path" type StatusEntry = { checkedAt: string status: "OK" | "DOWN" host: string detail: string } type StatusResult = { label: string url: string ok: boolean status: number checkedAt: string detail: string } const DEFAULT_RECETTE_SCRIPTS_DIR = "/home/malio/Malio-ops/RecetteScripts" function parseEnvFile(content: string) { const values: Record = {} for (const rawLine of content.split("\n")) { const line = rawLine.trim() if (!line || line.startsWith("#")) { continue } const separatorIndex = line.indexOf("=") if (separatorIndex === -1) { continue } const key = line.slice(0, separatorIndex).trim() const value = line.slice(separatorIndex + 1).trim() if (!key) { continue } values[key] = value.replace(/^['"]|['"]$/g, "") } return values } function getLogFileName(date: Date) { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, "0") const day = String(date.getDate()).padStart(2, "0") return `app_health_${year}-${month}-${day}.log` } function parseStatusLine(line: string): StatusEntry | null { const parts = line.split(" | ") if (parts.length < 4) { return null } const [checkedAt, status, host, ...detailParts] = parts if ((status !== "OK" && status !== "DOWN") || !host) { return null } return { checkedAt, status, host, detail: detailParts.join(" | ") } } function buildStatusResult(entry: StatusEntry): StatusResult { return { label: entry.host, url: `http://${entry.host}/`, ok: entry.status === "OK", status: entry.status === "OK" ? 200 : 0, checkedAt: entry.checkedAt, detail: entry.detail } } export default defineEventHandler(async () => { const recetteScriptsDir = process.env.RECETTE_SCRIPTS_DIR || DEFAULT_RECETTE_SCRIPTS_DIR const envFilePath = join(recetteScriptsDir, ".env") try { const envFileContent = await readFile(envFilePath, "utf8") const envValues = parseEnvFile(envFileContent) const logDir = envValues.APP_LOG_DIR if (!logDir) { throw createError({ statusCode: 500, statusMessage: "Variable APP_LOG_DIR manquante" }) } const logFilePath = join(logDir, getLogFileName(new Date())) const logFileContent = await readFile(logFilePath, "utf8") const latestEntriesByHost = new Map() for (const line of logFileContent.split("\n")) { const entry = parseStatusLine(line) if (!entry) { continue } latestEntriesByHost.set(entry.host, entry) } const configuredHosts = (envValues.APP_URLS || "") .split(/\s+/) .map((host) => host.trim()) .filter(Boolean) const orderedResults = configuredHosts .map((host) => latestEntriesByHost.get(host)) .filter((entry): entry is StatusEntry => Boolean(entry)) .map(buildStatusResult) const remainingResults = Array.from(latestEntriesByHost.entries()) .filter(([host]) => !configuredHosts.includes(host)) .map(([, entry]) => buildStatusResult(entry)) const results = [...orderedResults, ...remainingResults] if (results.length === 0) { throw createError({ statusCode: 503, statusMessage: "Aucun statut disponible" }) } return { results } } catch (error) { console.error("Erreur lecture status recette:", error) if ( typeof error === "object" && error !== null && "statusCode" in error && "statusMessage" in error ) { throw error } throw createError({ statusCode: 500, statusMessage: "Erreur lors de l'opération" }) } })