feat : ajout page monitoring

This commit is contained in:
2026-03-06 15:26:51 +01:00
parent 993524aa72
commit 8b5d4e9655
21 changed files with 11098 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
<template>
<section class="flex flex-col items-center p-4">
<p class="text-center text-xl font-semibold uppercase">{{ hostName }}</p>
<div class="relative h-[140px] w-[140px]" :class="statusColorClass">
<svg class="h-full w-full -rotate-90" viewBox="0 0 120 120" aria-label="Pourcentage restant">
<circle
class="fill-none stroke-[rgba(255,255,255,0.22)] [stroke-width:10]"
cx="60"
cy="60"
:r="chartRadius"
/>
<circle
class="fill-none stroke-[currentColor] [stroke-linecap:round] [stroke-width:10] transition-[stroke-dashoffset] duration-300"
cx="60"
cy="60"
:r="chartRadius"
:style="{ strokeDasharray: `${chartCircumference}`, strokeDashoffset: `${chartOffset}` }"
/>
</svg>
<div class="absolute inset-0 flex flex-col items-center justify-center">
<strong class="text-2xl leading-none">{{ remainingPercentText }}</strong>
</div>
</div>
<p class="mt-1 text-center text-sm font-semibold">{{ usedText }} / {{ totalText }}</p>
</section>
</template>
<script setup lang="ts">
defineProps<{
hostName: string
statusColorClass: string
chartRadius: number
chartCircumference: number
chartOffset: number
remainingPercentText: string
usedText: string
totalText: string
}>()
</script>
<style scoped>
.m-success {
color: rgb(var(--m-success));
}
.m-error {
color: rgb(var(--m-error));
}
</style>

131
components/Speedtest.vue Normal file
View File

@@ -0,0 +1,131 @@
<template>
<div class="bg-m-secondary w-[509px] h-[184px] mx-4 rounded-md shadow-md/50 shadow-black p-2">
<div class="mb-2 flex items-center justify-between">
<p class="font-bold text-3xl text-m-tertiary">
Speedtest
</p>
<IconifyIcon
icon="mdi:reload"
class="bg-m-tertiary text-2xl text-black rounded-md shadow-md/50 mr-1 cursor-pointer"
@Click="runTests"
/>
</div>
<div class="grid grid-cols-3 gap-3">
<div class="bg-m-tertiary w-[153px] h-[120px] rounded-md shadow-md/50 shadow-m-black">
<div class="flex items-center justify-center">
<IconifyIcon
icon="mdi:download"
class="text-m-primary text-2xl mt-2 ml-1"
/>
<p class="font-bold uppercase text-xl text-m-text mt-2 mr-1">
download
</p>
</div>
<div class="mx-2 flex flex-col items-center justify-center">
<span class="text-4xl">
{{ download !== null ? `${download}` : "..." }}
</span>
<p class="font-bold text-xl leading-tight">
Mbps
</p>
</div>
</div>
<div class="bg-m-tertiary w-[153px] h-[120px] rounded-md shadow-md/50 shadow-m-black">
<div class="flex items-center justify-center">
<IconifyIcon
icon="mdi:upload"
class="text-m-primary text-2xl mt-2 ml-1"
/>
<p class="font-bold uppercase text-xl text-m-text mt-2 mr-1">
upload
</p>
</div>
<div class="mx-2 flex flex-col items-center justify-center">
<span class="text-4xl">
{{ upload !== null ? `${upload}` : "..." }}
</span>
<p class="font-bold text-xl leading-tight">
Mbps
</p>
</div>
</div>
<div class="bg-m-tertiary w-[153px] h-[120px] rounded-md shadow-md/50 shadow-m-black">
<div class="flex items-center justify-center">
<IconifyIcon
icon="mdi:wifi"
class="text-m-primary text-2xl mt-2 ml-1"
/>
<p class="font-bold uppercase text-xl text-m-text mt-2 mr-1">
ping
</p>
</div>
<div class="mx-2 flex flex-col items-center justify-center">
<span class="text-4xl">
{{ ping !== null ? `${ping}` : "..." }}
</span>
<p class="font-bold text-xl leading-tight">
Ms
</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {Icon as IconifyIcon} from "@iconify/vue"
const ping = ref<number | null>(null)
const download = ref<number | null>(null)
const upload = ref<number | null>(null)
async function testDownload() {
const start = performance.now()
const res = await fetch('/api/download')
const blob = await res.blob()
const end = performance.now()
const size = blob.size
const seconds = (end - start) / 1000
download.value = Math.round((size * 8) / seconds / 1000000)
}
async function testUpload() {
const size = 5 * 1024 * 1024
const data = new Uint8Array(size)
const start = performance.now()
await fetch('/api/upload', {
method: 'POST',
body: data
})
const end = performance.now()
const seconds = (end - start) / 1000
upload.value = Math.round((size * 8) / seconds / 1000000)
}
async function testPing() {
const start = performance.now()
await fetch('/api/ping')
const end = performance.now()
ping.value = Math.round(end - start)
}
async function runTests() {
await testDownload()
await testUpload()
await testPing()
}
onMounted(() => {
runTests()
})
</script>

98
components/StatusSite.vue Normal file
View File

@@ -0,0 +1,98 @@
<template>
<div class="bg-m-secondary w-[250px] h-auto rounded-md mx-4 shadow-md/50 shadow-black pb-4">
<p class="font-bold text-3xl text-m-tertiary my-1 mx-3">
Status
</p>
<div
class="bg-m-tertiary w-[200px] h-auto rounded-md shadow-md/50 shadow-m-black mx-[25px] mb-3"
v-for="row in rows"
:key="`${row.label}-${row.url}`"
>
<p class="font-bold text-xl text-m-text mt-2 mx-2 mb-1">
{{ row.label }}
</p>
<div class="mx-2 flex items-center">
<span
class="inline-block h-[24px] w-[24px] rounded-full mr-2"
:class="statusClass(row.status)"
/>
<span class="font-semibold text-lg">
{{ statusLabel(row.status) }}
</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {onBeforeUnmount, onMounted, ref} from "vue"
interface StatusRow {
label: string
url: string
ok: boolean
status: number
checkedAt: string
error?: string
}
interface StatusResponse {
results: StatusRow[]
}
const props = withDefaults(
defineProps<{
endpoint?: string
refreshMs?: number
}>(),
{
endpoint: "/api/version-status",
refreshMs: 30000
}
)
const rows = ref<StatusRow[]>([])
let timer: ReturnType<typeof setInterval> | null = null
const statusClass = (status: number) => {
if (status === 200) return "bg-m-success"
if (status === 0) return "bg-m-error"
return "bg-m-error"
}
const statusLabel = (status: number) => {
if (status === 200) return "HTTP 200"
if (status === 0) return "Injoignable"
return `KO (HTTP ${status})`
}
const checkStatus = async () => {
try {
const data = await $fetch<StatusResponse>(props.endpoint)
rows.value = data.results
} catch (error) {
rows.value = [
{
label: "Erreur",
url: props.endpoint,
ok: false,
status: 0,
checkedAt: new Date().toISOString(),
error: error instanceof Error ? error.message : String(error)
}
]
}
}
onMounted(() => {
checkStatus()
timer = setInterval(checkStatus, props.refreshMs)
})
onBeforeUnmount(() => {
if (timer) {
clearInterval(timer)
timer = null
}
})
</script>