KI-AGENT: Systemstatus und Node Exporter ergänzen
This commit is contained in:
228
frontend/pages/administration/system.vue
Normal file
228
frontend/pages/administration/system.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<script setup lang="ts">
|
||||
import type { SystemStatus } from "~/composables/useAdmin"
|
||||
|
||||
const auth = useAuthStore()
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
const admin = useAdmin()
|
||||
|
||||
const loading = ref(true)
|
||||
const status = ref<SystemStatus | null>(null)
|
||||
|
||||
const serviceLabels: Record<string, string> = {
|
||||
backend: "Backend",
|
||||
database: "Datenbank",
|
||||
nodeExporter: "Node Exporter",
|
||||
matrix: "Matrix",
|
||||
minio: "Dateispeicher",
|
||||
}
|
||||
|
||||
const formatBytes = (value?: number | null) => {
|
||||
const bytes = Number(value || 0)
|
||||
if (!bytes) return "-"
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
||||
if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`
|
||||
return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`
|
||||
}
|
||||
|
||||
const formatDuration = (seconds?: number | null) => {
|
||||
const value = Number(seconds || 0)
|
||||
if (!value) return "-"
|
||||
|
||||
const days = Math.floor(value / 86400)
|
||||
const hours = Math.floor((value % 86400) / 3600)
|
||||
const minutes = Math.floor((value % 3600) / 60)
|
||||
|
||||
if (days) return `${days} d ${hours} h`
|
||||
if (hours) return `${hours} h ${minutes} min`
|
||||
return `${minutes} min`
|
||||
}
|
||||
|
||||
const loadStatus = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
status.value = await admin.getSystemStatus()
|
||||
} catch (err: any) {
|
||||
console.error("[administration/system]", err)
|
||||
toast.add({
|
||||
title: "Systemstatus konnte nicht geladen werden",
|
||||
description: err?.data?.error || err?.message || "Unbekannter Fehler",
|
||||
color: "red",
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const serviceRows = computed(() => {
|
||||
const services = status.value?.services || {}
|
||||
return Object.entries(services).map(([key, service]) => ({
|
||||
key,
|
||||
label: serviceLabels[key] || key,
|
||||
...service,
|
||||
}))
|
||||
})
|
||||
|
||||
const overallStatus = computed(() => {
|
||||
if (!status.value) return "unavailable"
|
||||
return serviceRows.value.every((service) => service.ok) ? "ok" : "warning"
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
if (!auth.user?.is_admin) {
|
||||
await router.push("/")
|
||||
return
|
||||
}
|
||||
|
||||
await loadStatus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar title="Administration: Systemstatus">
|
||||
<template #right>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
:loading="loading"
|
||||
@click="loadStatus"
|
||||
>
|
||||
Aktualisieren
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardPanelContent>
|
||||
<div class="space-y-6">
|
||||
<UAlert
|
||||
:icon="overallStatus === 'ok' ? 'i-heroicons-check-circle' : 'i-heroicons-exclamation-triangle'"
|
||||
:color="overallStatus === 'ok' ? 'success' : 'warning'"
|
||||
variant="soft"
|
||||
:title="overallStatus === 'ok' ? 'System läuft' : 'System prüfen'"
|
||||
:description="status?.checkedAt ? `Letzte Prüfung: ${new Date(status.checkedAt).toLocaleString('de-DE')}` : 'Noch keine Prüfung geladen.'"
|
||||
/>
|
||||
|
||||
<div class="grid gap-4 xl:grid-cols-3">
|
||||
<UCard :ui="{ root: 'rounded-lg' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-cpu-chip" class="size-5 text-primary" />
|
||||
<h2 class="text-base font-semibold text-highlighted">Server</h2>
|
||||
</div>
|
||||
</template>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div class="flex justify-between gap-3">
|
||||
<span class="text-muted">Host</span>
|
||||
<span class="truncate text-highlighted">{{ status?.server.hostname || "-" }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-3">
|
||||
<span class="text-muted">CPU</span>
|
||||
<span class="text-highlighted">{{ status?.server.cpuCount || "-" }} Kerne</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-3">
|
||||
<span class="text-muted">Load</span>
|
||||
<span class="text-highlighted">
|
||||
{{ status?.server.load.one ?? "-" }} · {{ status?.server.load.five ?? "-" }} · {{ status?.server.load.fifteen ?? "-" }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-3">
|
||||
<span class="text-muted">Uptime</span>
|
||||
<span class="text-highlighted">{{ formatDuration(status?.server.uptimeSeconds) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard :ui="{ root: 'rounded-lg' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-circle-stack" class="size-5 text-primary" />
|
||||
<h2 class="text-base font-semibold text-highlighted">Speicher</h2>
|
||||
</div>
|
||||
</template>
|
||||
<div class="space-y-4 text-sm">
|
||||
<div>
|
||||
<div class="mb-1 flex justify-between">
|
||||
<span class="text-muted">RAM</span>
|
||||
<span class="text-highlighted">{{ status?.server.memory.usedPercent ?? "-" }}%</span>
|
||||
</div>
|
||||
<UProgress :model-value="status?.server.memory.usedPercent || 0" />
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
{{ formatBytes(status?.server.memory.usedBytes) }} von {{ formatBytes(status?.server.memory.totalBytes) }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1 flex justify-between">
|
||||
<span class="text-muted">Root-Dateisystem</span>
|
||||
<span class="text-highlighted">{{ status?.server.disk.rootUsedPercent ?? "-" }}%</span>
|
||||
</div>
|
||||
<UProgress :model-value="status?.server.disk.rootUsedPercent || 0" />
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
{{ formatBytes(status?.server.disk.rootUsedBytes) }} von {{ formatBytes(status?.server.disk.rootTotalBytes) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard :ui="{ root: 'rounded-lg' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-server-stack" class="size-5 text-primary" />
|
||||
<h2 class="text-base font-semibold text-highlighted">Backend</h2>
|
||||
</div>
|
||||
</template>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div class="flex justify-between gap-3">
|
||||
<span class="text-muted">Status</span>
|
||||
<UBadge color="success" variant="soft">{{ status?.backend.status || "-" }}</UBadge>
|
||||
</div>
|
||||
<div class="flex justify-between gap-3">
|
||||
<span class="text-muted">Laufzeit</span>
|
||||
<span class="text-highlighted">{{ formatDuration(status?.backend.uptimeSeconds) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-3">
|
||||
<span class="text-muted">Node.js</span>
|
||||
<span class="text-highlighted">{{ status?.backend.nodeVersion || "-" }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-3">
|
||||
<span class="text-muted">Umgebung</span>
|
||||
<span class="text-highlighted">{{ status?.backend.environment || "-" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<UCard :ui="{ root: 'rounded-lg' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-signal" class="size-5 text-primary" />
|
||||
<h2 class="text-base font-semibold text-highlighted">Dienste</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="divide-y divide-default">
|
||||
<div
|
||||
v-for="service in serviceRows"
|
||||
:key="service.key"
|
||||
class="flex items-center justify-between gap-4 py-3"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<p class="font-medium text-highlighted">{{ service.label }}</p>
|
||||
<p class="truncate text-xs text-muted">
|
||||
{{ service.error || service.url || service.publicBaseUrl || service.status }}
|
||||
</p>
|
||||
</div>
|
||||
<UBadge
|
||||
:color="service.ok ? 'success' : 'error'"
|
||||
variant="soft"
|
||||
>
|
||||
{{ service.ok ? "OK" : "Fehler" }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</UDashboardPanelContent>
|
||||
</template>
|
||||
Reference in New Issue
Block a user