From 240cbe37fad22b1f051cc2b5db3c062af9ebaa9c Mon Sep 17 00:00:00 2001 From: cynfo3000 Date: Wed, 4 Mar 2026 00:11:43 +0100 Subject: [PATCH] Proxmox: feinere Tabelle, konfigurierbare Spalten (localStorage), kompaktere MiniBar+VmBadge --- frontend/src/pages/ProxmoxServers.jsx | 173 +++++++++++++++++++------- 1 file changed, 127 insertions(+), 46 deletions(-) diff --git a/frontend/src/pages/ProxmoxServers.jsx b/frontend/src/pages/ProxmoxServers.jsx index f5ae076..b863c35 100644 --- a/frontend/src/pages/ProxmoxServers.jsx +++ b/frontend/src/pages/ProxmoxServers.jsx @@ -1,10 +1,40 @@ -import { useEffect, useState, useMemo } from 'react' +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 } from 'lucide-react' +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([]) @@ -15,6 +45,9 @@ export default function ProxmoxServers() { 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()]) @@ -59,6 +92,25 @@ export default function ProxmoxServers() { } } + // 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] @@ -143,7 +195,7 @@ export default function ProxmoxServers() { const SortHeader = ({ label, k, className = '' }) => ( toggleSort(k)} > @@ -176,27 +228,58 @@ export default function ProxmoxServers() { className="w-full bg-gray-900 border border-gray-800 rounded pl-10 pr-3 py-2 text-sm text-white placeholder-gray-600 focus:outline-none focus:border-orange-500" /> +
+ + {showColMenu && ( +
+
Sichtbare Spalten
+ {ALL_COLUMNS.map(col => ( + + ))} +
+ )} +
{loading ? (
Laden...
) : (
- +
- - - - - - - - - - - - + + {isColVisible('customer') && } + {isColVisible('name') && } + {isColVisible('hostname') && } + {isColVisible('version') && } + {isColVisible('ip') && } + {isColVisible('uptime') && } + {isColVisible('cpu') && } + {isColVisible('ram') && } + {isColVisible('disk') && } + {isColVisible('vms') && } + {isColVisible('containers') && } + {isColVisible('lastresponse') && } + {isColVisible('agentversion') && } @@ -206,30 +289,28 @@ export default function ProxmoxServers() { onClick={() => setSelectedId(a.id)} className={`hover:bg-gray-800/50 transition-colors cursor-pointer ${selectedId === a.id ? 'bg-gray-800/70' : ''}`} > - - - - - - - - - - - - + + {isColVisible('customer') && ( + + )} + {isColVisible('name') && } + {isColVisible('hostname') && } + {isColVisible('version') && } + {isColVisible('ip') && } + {isColVisible('uptime') && } + {isColVisible('cpu') && } + {isColVisible('ram') && } + {isColVisible('disk') && } + {isColVisible('vms') && } + {isColVisible('containers') && } + {isColVisible('lastresponse') && ( + + )} + {isColVisible('agentversion') && } ))} @@ -347,9 +428,9 @@ function AddServerDialog({ customers, result, onAdd, onClose }) { } function VmBadge({ running, total }) { - if (total === 0) return + if (total === 0) return return ( - + {running} /{total} @@ -357,14 +438,14 @@ function VmBadge({ running, total }) { } function MiniBar({ value }) { - if (value === null || value === undefined) return + if (value === null || value === undefined) return const color = value > 90 ? 'bg-red-500' : value > 70 ? 'bg-yellow-500' : value > 50 ? 'bg-blue-500' : 'bg-green-500' return ( -
-
+
+
- {value.toFixed(0)}% + {value.toFixed(0)}%
) }
HOSTNAMEIPHOSTNAMEIPLETZTER KONTAKTAGENT
- {a.customer ? {a.customerNumber} : } - {a.name}{a.hostname}{a.pveVersion || '—'}{a.ip}{a.uptime ? formatUptime(a.uptime) : '—'} - - - - - - - - - - + {a.customer ? {a.customerNumber} : } + {a.name}{a.hostname}{a.pveVersion || '—'}{a.ip}{a.uptime ? formatUptime(a.uptime) : '—'} + {a.last_heartbeat ? new Date(a.last_heartbeat).toLocaleString('de-DE') : '—'} + {a.agent_version || '—'}