import { useEffect, useState, useMemo, useRef } from 'react' import api from '../api/client' import StatusBadge from '../components/StatusBadge' import WindowsPanel from '../components/WindowsPanel' import { Search, ChevronUp, ChevronDown, Settings2, X } from 'lucide-react' const ALL_COLUMNS = [ { id: 'customer', label: 'Kunde', default: true }, { id: 'name', label: 'Name', default: true, locked: true }, { id: 'hostname', label: 'Hostname', default: false }, { id: 'os', label: 'OS', default: true }, { id: 'version', label: 'Agent 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 (C:)', default: true }, { id: 'lastresponse', label: 'Letzter Kontakt', default: false }, ] const STORAGE_KEY = 'rmm-windows-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)) } function formatUptime(seconds) { if (!seconds) return '—' const d = Math.floor(seconds / 86400) const h = Math.floor((seconds % 86400) / 3600) const m = Math.floor((seconds % 3600) / 60) if (d > 0) return `${d}d ${h}h` return `${h}h ${m}m` } export default function Windows() { const [agents, setAgents] = useState([]) const [customers, setCustomers] = useState([]) const [agentDetails, setAgentDetails] = 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 [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 || []).filter(ag => ag.platform === 'windows')) setCustomers(c || []) }) .finally(() => setLoading(false)) } useEffect(() => { reload() const iv = setInterval(reload, 30000) return () => clearInterval(iv) }, []) useEffect(() => { agents.forEach(a => { if (!agentDetails[a.id]) { api.getAgent(a.id).then(d => setAgentDetails(prev => ({ ...prev, [a.id]: d })) ) } }) }, [agents]) // 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 customerMap = useMemo(() => Object.fromEntries((customers || []).map(c => [c.id, c])), [customers]) const toggleSort = (key) => { if (sortKey === key) setSortDir(d => d === 'asc' ? 'desc' : 'asc') else { setSortKey(key); setSortDir('asc') } } 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(() => agents.map(a => { const d = agentDetails[a.id] const win = d?.system_data?.windows const cust = a.customer_id ? customerMap[a.customer_id] : null const memPct = win?.memory?.total_bytes > 0 ? ((win.memory.total_bytes - (win.memory.available_bytes ?? 0)) / win.memory.total_bytes * 100) : null // Ersten FIXED-Disk nehmen (meistens C:) const disk = win?.disks?.[0] const diskPct = disk?.used_percent ?? null return { ...a, customer: cust, customerName: cust?.name || '', customerNumber: cust?.number || '', os: win?.os_version || '—', cpuPct: win?.cpu?.load_percent ?? null, memPct, diskPct, uptime: win?.uptime_seconds || 0, } }), [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 = [...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 'os': va = a.os; vb = b.os; break case 'version': va = a.agent_version || ''; vb = b.agent_version || ''; 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.memPct ?? 999) - (b.memPct ?? 999) : (b.memPct ?? -1) - (a.memPct ?? -1) case 'disk': return sortDir === 'asc' ? (a.diskPct ?? 999) - (b.diskPct ?? 999) : (b.diskPct ?? -1) - (a.diskPct ?? -1) 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('os') &&IP | } {isColVisible('uptime') &&LETZTER KONTAKT | }||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| {a.customer ? {a.customerNumber} : —} | )} {isColVisible('name') &&{a.name} | } {isColVisible('hostname') &&{a.hostname || '—'} | } {isColVisible('os') &&{a.os} | } {isColVisible('version') &&{a.agent_version ? `v${a.agent_version}` : '—'} | } {isColVisible('ip') &&{a.ip || '—'} | } {isColVisible('uptime') &&{formatUptime(a.uptime)} | } {isColVisible('cpu') &&{a.last_heartbeat ? new Date(a.last_heartbeat).toLocaleString('de-DE') : '—'} | )}