From 4e21fe3c4e39b17deb92af961e3184c3a697d4d7 Mon Sep 17 00:00:00 2001 From: cynfo3000 Date: Tue, 10 Mar 2026 02:03:40 +0100 Subject: [PATCH] feat: Windows-Seite in Navigation + Agents-Filter excludes Windows --- frontend/src/App.jsx | 2 + frontend/src/layouts/AppLayout.jsx | 2 + frontend/src/pages/Agents.jsx | 2 +- frontend/src/pages/Windows.jsx | 157 +++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/Windows.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5a863a6..1fd349b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5,6 +5,7 @@ import AppLayout from './layouts/AppLayout' import Login from './pages/Login' import Dashboard from './pages/Dashboard' import Agents from './pages/Agents' +import Windows from './pages/Windows' import AgentDetail from './pages/AgentDetail' import ProxmoxServers from './pages/ProxmoxServers' import Customers from './pages/Customers' @@ -53,6 +54,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/layouts/AppLayout.jsx b/frontend/src/layouts/AppLayout.jsx index 4ef0d8e..c2e9d4c 100644 --- a/frontend/src/layouts/AppLayout.jsx +++ b/frontend/src/layouts/AppLayout.jsx @@ -14,11 +14,13 @@ import { Download, HardDrive, ClipboardList, + Monitor, } from 'lucide-react' const navItems = [ { path: '/', label: 'Dashboard', icon: LayoutDashboard }, { path: '/agents', label: 'Firewalls', icon: Server }, + { path: '/windows', label: 'Windows', icon: Monitor }, { path: '/proxmox', label: 'Proxmox', icon: HardDrive }, { path: '/tunnels', label: 'Tunnel', icon: Cable }, { path: '/firmware', label: 'Firmware', icon: Download }, diff --git a/frontend/src/pages/Agents.jsx b/frontend/src/pages/Agents.jsx index 6576348..007a43d 100644 --- a/frontend/src/pages/Agents.jsx +++ b/frontend/src/pages/Agents.jsx @@ -126,7 +126,7 @@ export default function Agents() { const cust = a.customer_id ? customerMap[a.customer_id] : null // Nur OPNsense/FreeBSD Agents anzeigen - if (a.platform === 'linux') return null + if (a.platform === 'linux' || a.platform === 'windows') return null const rootDisk = sys?.disks?.find((d) => d.mount_point === '/') const diskPct = rootDisk && rootDisk.total_bytes > 0 diff --git a/frontend/src/pages/Windows.jsx b/frontend/src/pages/Windows.jsx new file mode 100644 index 0000000..21bdba5 --- /dev/null +++ b/frontend/src/pages/Windows.jsx @@ -0,0 +1,157 @@ +import { useEffect, useState, useMemo } from 'react' +import api from '../api/client' +import StatusBadge from '../components/StatusBadge' +import WindowsPanel from '../components/WindowsPanel' +import { Search, Monitor } from 'lucide-react' + +function fmtPct(v) { + if (v === null || v === undefined) return '—' + return `${Number(v).toFixed(0)}%` +} + +function fmtUptime(s) { + if (!s) return '—' + const d = Math.floor(s / 86400) + const h = Math.floor((s % 86400) / 3600) + return d > 0 ? `${d}d ${h}h` : `${h}h` +} + +function fmtBytes(b) { + if (!b) return '—' + if (b >= 1073741824) return `${(b / 1073741824).toFixed(1)} GB` + return `${(b / 1048576).toFixed(0)} MB` +} + +export default function Windows() { + const [agents, setAgents] = useState([]) + const [customers, setCustomers] = useState([]) + const [details, setDetails] = useState({}) + const [search, setSearch] = useState('') + const [selectedId, setSelectedId] = useState(null) + const [loading, setLoading] = useState(true) + + const reload = () => { + Promise.all([api.getAgents(), api.getCustomers()]).then(([a, c]) => { + const win = a.filter(ag => ag.platform === 'windows') + setAgents(win) + setCustomers(c) + setLoading(false) + win.forEach(ag => { + api.getAgent(ag.id).then(d => setDetails(prev => ({ ...prev, [ag.id]: d }))) + }) + }) + } + + useEffect(() => { reload() }, []) + + const customerMap = useMemo(() => + Object.fromEntries(customers.map(c => [c.id, c])), [customers]) + + const rows = useMemo(() => agents.map(a => { + const d = details[a.id] + const sys = d?.system_data + const win = sys?.windows + const cust = a.customer_id ? customerMap[a.customer_id] : null + const memUsed = win?.memory?.total_bytes > 0 + ? ((win.memory.total_bytes - win.memory.available_bytes) / win.memory.total_bytes * 100) + : null + const disk = win?.disks?.[0] + return { + id: a.id, name: a.name, status: a.status, + version: a.agent_version, + customer: cust ? `${cust.number} — ${cust.name}` : '—', + customer_id: a.customer_id, + os: win?.os_version?.replace('Windows ', 'Win ') || '—', + cpu: win?.cpu?.load_percent != null ? fmtPct(win.cpu.load_percent) : '—', + ram: memUsed != null ? fmtPct(memUsed) : '—', + disk: disk?.used_percent != null ? fmtPct(disk.used_percent) : '—', + uptime: fmtUptime(win?.uptime_seconds), + lastHB: a.last_heartbeat + ? new Date(a.last_heartbeat).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) + : '—', + } + }), [agents, details, customerMap]) + + const filtered = useMemo(() => { + if (!search) return rows + const q = search.toLowerCase() + return rows.filter(r => + r.name.toLowerCase().includes(q) || + r.customer.toLowerCase().includes(q) || + r.os.toLowerCase().includes(q) + ) + }, [rows, search]) + + if (loading) return
Laden...
+ + return ( +
+ {/* Toolbar */} +
+ + Windows Agents + ({agents.length}) +
+
+ + setSearch(e.target.value)} + placeholder="Suchen..." + className="pl-8 pr-3 py-1.5 bg-gray-800 border border-gray-700 rounded text-sm text-white focus:outline-none focus:border-sky-500 w-56" + /> +
+
+ + {/* Tabelle */} +
+ {filtered.length === 0 ? ( +
+ {agents.length === 0 ? 'Keine Windows Agents registriert' : 'Keine Ergebnisse'} +
+ ) : ( + + + + {['Status', 'Name', 'Kunde', 'OS', 'Version', 'CPU', 'RAM', 'Disk', 'Uptime', 'Letzter Kontakt'].map(h => ( + + ))} + + + + {filtered.map(r => ( + setSelectedId(r.id)} + className={`hover:bg-gray-800/50 cursor-pointer transition-colors ${selectedId === r.id ? 'bg-gray-800/70' : ''}`} + > + + + + + + + + + + + + ))} + +
{h}
+ + {r.name}{r.customer}{r.os}{r.version ? `v${r.version}` : '—'}{r.cpu}{r.ram}{r.disk}{r.uptime}{r.lastHB}
+ )} +
+ + {/* Panel */} + {selectedId && ( + setSelectedId(null)} + onReload={reload} + /> + )} +
+ ) +}