'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 && (
{isRefreshing ? t('card.refreshing') : t('card.refresh')}
)}
>
) : 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) => (
))}
>
)}
);
}