'use client'; import { useState } from 'react'; import useSWR, { mutate } from 'swr'; import NodeList from './NodeList'; import VMList from './VMList'; import LXCList from './LXCList'; import axios from 'axios'; import { SWRConfig } from 'swr'; import ConsolePanel from './ConsolePanel'; const POLL_INTERVAL = Number(process.env.NEXT_PUBLIC_API_POLL_INTERVAL || 30000); /** * Fetch helper for SWR. * @param {string} url * @returns {Promise<{ data: *, error: string | null }>} */ async function fetchData(url) { try { const res = await axios.get(url); return res.data; } catch (err) { const status = err.response?.status || 'Unknown'; const message = err.response?.data?.message || err.response?.data?.errors || err.message || String(err); throw new Error(`API error ${status}: ${message}`); } } /** @returns {import('react').ReactNode} */ export default function Dashboard() { const [activeTab, setActiveTab] = useState('nodes'); const [activeConsole, setActiveConsole] = useState(null); const [powerActionStatus, setPowerActionStatus] = useState(null); /** * @param {string} node * @param {number} vmid * @param {string} type * @param {string} action */ const handlePowerAction = async (node, vmid, type, action) => { setPowerActionStatus({ text: `${action.charAt(0).toUpperCase() + action.slice(1)}ing ${type === 'qemu' ? 'VM' : 'LXC'} ${vmid}...`, type: 'pending' }); try { await axios.post('/api/power', { node, vmid, type, action }); // Immediately refetch all data instead of waiting for polling interval await mutate('/api/nodes'); await mutate('/api/vms'); await mutate('/api/lxc'); await mutate('/api/status'); setPowerActionStatus({ text: `${action.charAt(0).toUpperCase() + action.slice(1)} successful`, type: 'success' }); } catch (err) { setPowerActionStatus({ text: err.message || `${action.charAt(0).toUpperCase() + action.slice(1)} failed`, type: 'error' }); } }; const { data: nodesData, error: nodesError } = useSWR('/api/nodes', fetchData, { refreshInterval: POLL_INTERVAL, }); const { data: vmsData, error: vmsError } = useSWR('/api/vms', fetchData, { refreshInterval: POLL_INTERVAL, }); const { data: lxcData, error: lxcError } = useSWR('/api/lxc', fetchData, { refreshInterval: POLL_INTERVAL, }); const { data: summaryData, error: summaryError } = useSWR('/api/status', fetchData, { refreshInterval: POLL_INTERVAL, }); const nodes = nodesData?.data ?? []; const vms = vmsData?.data ?? []; const lxc = lxcData?.data ?? []; const summary = summaryData?.data ?? null; const tabs = [ { id: 'nodes', label: 'Nodes' }, { id: 'vms', label: 'VMs' }, { id: 'lxc', label: 'LXCs' }, ...(activeConsole ? [{ id: 'console', label: `Console: ${activeConsole.name}` }] : []), ]; return (
{/* Header */}

Proxmox Monitor

{nodes.length} node{nodes.length !== 1 ? 's' : ''} • {summary?.runningVMs ?? 0} running • Updated{' '} {new Date().toLocaleTimeString()}

{/* Summary Cards */} {summary && !summaryError && (
Nodes
{summary.totalNodes}
Running VMs
{summary.runningVMs}
Stopped VMs
{summary.stoppedVMs}
Total Memory
{summary.totalMemory?.total ? `${(summary.totalMemory.used / (1024 ** 3)).toFixed(1)} / ${(summary.totalMemory.total / (1024 ** 3)).toFixed(1)} GB` : 'N/A'}
)} {(summaryError || nodesError) && (
{summaryError || nodesError}
)} {powerActionStatus && (
{powerActionStatus.text}
)} {/* Tab navigation */}
{tabs.map((tab) => ( ))}
{/* Tab content */}
{activeTab === 'nodes' && } {activeTab === 'vms' && { setActiveConsole(vm); setActiveTab('console'); }} />} {activeTab === 'lxc' && { setActiveConsole(lxc); setActiveTab('console'); }} />} {activeTab === 'console' && activeConsole && ( setActiveConsole(null)} /> )}
{/* Footer */}
Polling every {POLL_INTERVAL / 1000}s • Proxmox VE Monitor
); }