'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 (
{nodes.length} node{nodes.length !== 1 ? 's' : ''} • {summary?.runningVMs ?? 0} running • Updated{' '} {new Date().toLocaleTimeString()}