'use client'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslations } from 'next-intl'; import Link from 'next/link'; import { PageHeader } from '@/components/layout/page-header'; import { useAuthStore } from '@/lib/stores/auth-store'; import { listServers, pollServer, type ProxmoxServer } from '@/lib/proxmox-api'; import { HealthBar } from '@/components/proxmox/HealthBar'; import { sortServersByHealth, summarizeHealth } from '@/components/proxmox/proxmox-status'; import { ServerCard } from './components/ServerCard'; import { WELL } from '@/components/proxmox/status-styles'; /** Relative Zeitangaben („vor 3 Min.“) bleiben stimmig, ohne neu zu laden. */ const CLOCK_TICK_MS = 60_000; function RefreshIcon({ spinning }: { spinning: boolean }) { return ( ); } function SkeletonCard() { return (
); } /** * Modulseite (quick-260924-h7x, vorher 260923-dhh Aufgabe 6) — liest * ausschliesslich aus dem Zwischenlager, das `GET servers` liefert; kein * Live-Zugriff bei Proxmox von hier aus (D-05). * * Aufbau: Kopf, Gesundheitsbalken, Kartenraster sortiert nach Zustand * (down, warn, ok, idle, orphan, darin `position`). „Jetzt aktualisieren“ * loest je Server eine Abfrage aus und laedt danach neu; der Knopf erscheint * nur fuer Admins, weil `POST servers/:id/poll` `@Roles(ADMIN, SUPER_ADMIN)` * verlangt (260923-le6). */ export default function ProxmoxPage() { const t = useTranslations('proxmox'); const user = useAuthStore((s) => s.user); const isAdmin = user?.role === 'ADMIN' || user?.role === 'SUPER_ADMIN'; const [servers, setServers] = useState(null); const [error, setError] = useState(null); const [isRefreshing, setIsRefreshing] = useState(false); const [now, setNow] = useState(() => Date.now()); const reload = useCallback(() => { listServers() .then((list) => { setServers(list); setNow(Date.now()); }) .catch(() => setError(t('loadError'))); }, [t]); useEffect(() => { reload(); }, [reload]); useEffect(() => { const id = window.setInterval(() => setNow(Date.now()), CLOCK_TICK_MS); return () => window.clearInterval(id); }, []); const handleRefresh = async () => { if (!servers || servers.length === 0) return; setIsRefreshing(true); try { await Promise.all(servers.map((server) => pollServer(server.id).catch(() => undefined))); reload(); } finally { setIsRefreshing(false); } }; const sorted = useMemo(() => (servers ? sortServersByHealth(servers, now) : []), [servers, now]); const counts = useMemo(() => summarizeHealth(servers ?? [], now), [servers, now]); return (
{t('card.settingsNav')} {servers !== null && servers.length > 0 && ( )} ) : undefined } /> {error && (

{error}

)} {!error && servers === null && (
{t('loading')}
)} {!error && servers !== null && servers.length === 0 && (

{t('emptyState')}

{isAdmin && ( {t('card.settingsLink')} )}
)} {!error && servers !== null && servers.length > 0 && ( <> {/* Spaltenfluss statt Zeilenraster: eine kurze Karte (z. B. "nicht erreichbar") liesse im Raster eine Luecke bis zur hoeheren Nachbarkarte; so rueckt die naechste Karte direkt nach. */}
    {sorted.map((server) => (
  • ))}
)}
); }