import { useEffect, useState, useMemo, useRef } from 'react' import api from '../api/client' import StatusBadge from '../components/StatusBadge' import ProxmoxPanel from '../components/ProxmoxPanel' import { Search, ChevronUp, ChevronDown, Plus, X, Settings2 } from 'lucide-react' import InstallGuide from '../components/InstallGuide' const ALL_COLUMNS = [ { id: 'customer', label: 'Kunde', default: true }, { id: 'name', label: 'Name', default: true, locked: true }, { id: 'hostname', label: 'Hostname', default: false }, { id: 'version', label: 'PVE Version', default: true }, { id: 'ip', label: 'IP', default: true }, { id: 'uptime', label: 'Uptime', default: true }, { id: 'cpu', label: 'CPU', default: true }, { id: 'ram', label: 'RAM', default: true }, { id: 'disk', label: 'Disk', default: true }, { id: 'vms', label: 'VMs', default: true }, { id: 'containers', label: 'Container', default: true }, { id: 'lastresponse', label: 'Letzter Kontakt', default: false }, { id: 'agentversion', label: 'Agent Version', default: false }, ] const STORAGE_KEY = 'rmm-proxmox-columns' function loadColumns() { try { const saved = localStorage.getItem(STORAGE_KEY) if (saved) return JSON.parse(saved) } catch {} return ALL_COLUMNS.filter(c => c.default).map(c => c.id) } function saveColumns(cols) { localStorage.setItem(STORAGE_KEY, JSON.stringify(cols)) } export default function ProxmoxServers() { const [agents, setAgents] = useState([]) const [customers, setCustomers] = useState([]) const [search, setSearch] = useState('') const [loading, setLoading] = useState(true) const [sortKey, setSortKey] = useState('customer') const [sortDir, setSortDir] = useState('asc') const [selectedId, setSelectedId] = useState(null) const [showAddDialog, setShowAddDialog] = useState(false) const [addResult, setAddResult] = useState(null) const [visibleCols, setVisibleCols] = useState(loadColumns) const [showColMenu, setShowColMenu] = useState(false) const colMenuRef = useRef(null) const reload = () => { Promise.all([api.getAgents(), api.getCustomers()]) .then(([a, c]) => { setAgents(a || []) setCustomers(c || []) }) .finally(() => setLoading(false)) } useEffect(() => { reload() const iv = setInterval(reload, 30000) return () => clearInterval(iv) }, []) const customerMap = useMemo(() => { const m = {} customers.forEach((c) => (m[c.id] = c)) return m }, [customers]) // Enrich agents with system data summaries const [agentDetails, setAgentDetails] = useState({}) useEffect(() => { agents.forEach((a) => { if (!agentDetails[a.id]) { api.getAgent(a.id).then((d) => { setAgentDetails((prev) => ({ ...prev, [a.id]: d })) }) } }) }, [agents]) const toggleSort = (key) => { if (sortKey === key) { setSortDir(sortDir === 'asc' ? 'desc' : 'asc') } else { setSortKey(key) setSortDir('asc') } } // Close column menu on click outside useEffect(() => { const handler = (e) => { if (colMenuRef.current && !colMenuRef.current.contains(e.target)) setShowColMenu(false) } document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, []) const toggleCol = (id) => { const col = ALL_COLUMNS.find(c => c.id === id) if (col?.locked) return const next = visibleCols.includes(id) ? visibleCols.filter(c => c !== id) : [...visibleCols, id] setVisibleCols(next) saveColumns(next) } const isColVisible = (id) => visibleCols.includes(id) const enriched = useMemo(() => { return agents.map((a) => { const detail = agentDetails[a.id] const sys = detail?.system_data const proxmox = sys?.proxmox const cust = a.customer_id ? customerMap[a.customer_id] : null // Nur Proxmox-Agents anzeigen if (!proxmox?.available) return null const rootDisk = sys?.disks?.find((d) => d.mount_point === '/') const diskPct = rootDisk && rootDisk.total_bytes > 0 ? (rootDisk.used_bytes / rootDisk.total_bytes * 100) : null const ramPct = sys?.memory?.total_bytes > 0 ? (sys.memory.used_bytes / sys.memory.total_bytes * 100) : null const vmsRunning = proxmox.vms?.filter(vm => vm.status === 'running').length || 0 const vmsTotal = proxmox.vms?.length || 0 const ctRunning = proxmox.containers?.filter(ct => ct.status === 'running').length || 0 const ctTotal = proxmox.containers?.length || 0 return { ...a, customer: cust, customerName: cust?.name || '', customerNumber: cust?.number || '', cpuPct: sys?.cpu?.usage_percent ?? null, ramPct, diskPct, uptime: sys?.uptime_seconds || 0, pveVersion: proxmox.version || '', vms: `${vmsRunning}/${vmsTotal}`, vmsRunning, vmsTotal, containers: `${ctRunning}/${ctTotal}`, ctRunning, ctTotal, sys, proxmox, } }).filter(Boolean) // Entferne null Werte }, [agents, agentDetails, customerMap]) const filtered = useMemo(() => { let list = enriched if (search) { const q = search.toLowerCase() list = list.filter((a) => a.name.toLowerCase().includes(q) || a.ip.toLowerCase().includes(q) || a.hostname.toLowerCase().includes(q) || a.customerName.toLowerCase().includes(q) || a.customerNumber.toLowerCase().includes(q) ) } list.sort((a, b) => { let va, vb switch (sortKey) { case 'customer': va = a.customerNumber; vb = b.customerNumber; break case 'name': va = a.name; vb = b.name; break case 'ip': va = a.ip; vb = b.ip; break case 'version': va = a.pveVersion; vb = b.pveVersion; break case 'uptime': return sortDir === 'asc' ? a.uptime - b.uptime : b.uptime - a.uptime case 'cpu': return sortDir === 'asc' ? (a.cpuPct ?? 999) - (b.cpuPct ?? 999) : (b.cpuPct ?? -1) - (a.cpuPct ?? -1) case 'ram': return sortDir === 'asc' ? (a.ramPct ?? 999) - (b.ramPct ?? 999) : (b.ramPct ?? -1) - (a.ramPct ?? -1) case 'disk': return sortDir === 'asc' ? (a.diskPct ?? 999) - (b.diskPct ?? 999) : (b.diskPct ?? -1) - (a.diskPct ?? -1) case 'vms': return sortDir === 'asc' ? a.vmsTotal - b.vmsTotal : b.vmsTotal - a.vmsTotal case 'containers': return sortDir === 'asc' ? a.ctTotal - b.ctTotal : b.ctTotal - a.ctTotal case 'status': va = a.status; vb = b.status; break default: va = a.name; vb = b.name } if (typeof va === 'string') { const cmp = va.localeCompare(vb) return sortDir === 'asc' ? cmp : -cmp } return 0 }) return list }, [enriched, search, sortKey, sortDir]) const SortHeader = ({ label, k, className = '' }) => (
| {isColVisible('customer') && | HOSTNAME | } {isColVisible('version') &&IP | } {isColVisible('uptime') &&LETZTER KONTAKT | } {isColVisible('agentversion') &&AGENT | }|||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {a.customer ? {a.customerNumber} : —} | )} {isColVisible('name') &&{a.name} | } {isColVisible('hostname') &&{a.hostname} | } {isColVisible('version') &&{a.pveVersion || '—'} | } {isColVisible('ip') &&{a.ip} | } {isColVisible('uptime') &&{a.uptime ? formatUptime(a.uptime) : '—'} | } {isColVisible('cpu') &&{a.last_heartbeat ? new Date(a.last_heartbeat).toLocaleString('de-DE') : '—'} | )} {isColVisible('agentversion') &&{a.agent_version || '—'} | }
{error}
} > ) : ( <>