Files
Supervisor/pages/index.vue
2026-03-09 10:50:41 +01:00

173 lines
5.0 KiB
Vue

<template>
<NuxtLayout name="default">
<template #sidebar>
<div class="flex flex-col">
<DiagramStorage
v-for="item in diagramItems"
:key="item.key"
:loading="loading"
:host-name="item.hostName"
:status-color-class="item.statusColorClass"
:chart-radius="chartRadius"
:chart-circumference="chartCircumference"
:chart-offset="item.chartOffset"
:remaining-percent-text="item.remainingPercentText"
:used-text="item.usedText"
:total-text="item.totalText"
/>
</div>
</template>
<p class="font-bold text-4xl my-6 mx-4">Écran de monitoring</p>
<div class="flex">
<div class="flex flex-col gap-4">
<StatusSite />
<BackupButtonSee @select="selectedBackup = $event" />
</div>
<div class="flex flex-col gap-4">
<Speedtest />
<BackupList :folder="selectedBackup" />
<MessageDiscord/>
</div>
</div>
</NuxtLayout>
</template>
<script setup lang="ts">
definePageMeta({layout: false})
import {computed, onMounted, ref} from "vue"
type SourceKey = "remote" | "local"
type DiskCommandResult = { ok: boolean; output: string }
type DiskApiResponse = {
remote?: string | DiskCommandResult
local?: string | DiskCommandResult
}
const selectedBackup = ref<string | null>(null)
const rawResults = ref<Record<SourceKey, string>>({
remote: "",
local: ""
})
const loading = ref(false)
const chartRadius = 52
const chartCircumference = 2 * Math.PI * chartRadius
const getHostName = (output: string, fallback: string) => {
const hostMatch = output.match(/Name:\s*(.+)/i)
return hostMatch?.[1]?.trim() || fallback
}
const getDiskValues = (output: string) => {
if (!output || output.startsWith("Erreur:")) return null
const availableLine = output
.split("\n")
.find((line) => line.toLowerCase().includes("espace disponible"))
const usageLine = output
.split("\n")
.find((line) => line.toLowerCase().includes("espace utilise / espace total"))
const availableRaw = availableLine?.match(/:\s*(\d+(?:[.,]\d+)?)\s*GB/i)?.[1]
const usedRaw = usageLine?.match(/:\s*(\d+(?:[.,]\d+)?)\s*GB/i)?.[1]
const totalRaw = usageLine?.match(/\/\s*(\d+(?:[.,]\d+)?)\s*GB/i)?.[1]
const availableGb = availableRaw ? Number.parseFloat(availableRaw.replace(",", ".")) : null
const usedGb = usedRaw ? Number.parseFloat(usedRaw.replace(",", ".")) : null
const totalGb = totalRaw ? Number.parseFloat(totalRaw.replace(",", ".")) : null
if (
availableGb === null ||
usedGb === null ||
totalGb === null ||
!Number.isFinite(availableGb) ||
!Number.isFinite(usedGb) ||
!Number.isFinite(totalGb) ||
totalGb <= 0
) {
return null
}
return {availableGb, usedGb, totalGb}
}
const getOutputText = (entry: unknown) => {
if (typeof entry === "string") return entry
if (entry && typeof entry === "object" && "output" in entry) {
const output = (entry as DiskCommandResult).output
return typeof output === "string" ? output : String(output)
}
return ""
}
const diagramItems = computed(() => {
return [
{ key: "remote" as const, fallbackHost: "Serveur distant", output: rawResults.value.remote },
{ key: "local" as const, fallbackHost: "Machine locale", output: rawResults.value.local }
].map((item) => {
const diskValues = getDiskValues(item.output)
const remainingPercent =
diskValues === null
? null
: Math.max(
0,
Math.min(100, Math.round((diskValues.availableGb / diskValues.totalGb) * 100))
)
const chartOffset = chartCircumference - ((remainingPercent ?? 0) / 100) * chartCircumference
const statusColorClass =
remainingPercent !== null && remainingPercent <= 30 ? "m-error" : "m-success"
return {
key: item.key,
hostName: getHostName(item.output, item.fallbackHost),
statusColorClass,
chartOffset,
remainingPercentText:
loading.value ? "..." : remainingPercent === null ? "--%" : `${remainingPercent}%`,
usedText: loading.value ? "..." : diskValues ? `${diskValues.usedGb.toFixed(2)} GB` : "--",
totalText: loading.value ? "..." : diskValues ? `${diskValues.totalGb.toFixed(2)} GB` : "--"
}
})
})
const runScript = async () => {
loading.value = true
rawResults.value = {
remote: "",
local: ""
}
try {
const output = await $fetch<DiskApiResponse | string>("/api/disk")
if (typeof output === "string") {
rawResults.value = {
remote: output,
local: "Erreur: sortie locale indisponible"
}
return
}
rawResults.value = {
remote: getOutputText(output.remote),
local: getOutputText(output.local)
}
} catch (error) {
const message = `Erreur: ${error instanceof Error ? error.message : String(error)}`
rawResults.value = {
remote: message,
local: message
}
} finally {
loading.value = false
}
}
onMounted(() => {
runScript()
})
</script>