173 lines
5.0 KiB
Vue
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>
|