161 lines
3.7 KiB
TypeScript
161 lines
3.7 KiB
TypeScript
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<string, string> = {}
|
|
|
|
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<string, StatusEntry>()
|
|
|
|
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"
|
|
})
|
|
}
|
|
})
|