feat : ajout page monitoring
This commit is contained in:
50
components/DiagramStorage.vue
Normal file
50
components/DiagramStorage.vue
Normal 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
131
components/Speedtest.vue
Normal 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
98
components/StatusSite.vue
Normal 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>
|
||||
Reference in New Issue
Block a user