diff --git a/frontend/src/pages/Agents.jsx b/frontend/src/pages/Agents.jsx index a5a8430..c64f71a 100644 --- a/frontend/src/pages/Agents.jsx +++ b/frontend/src/pages/Agents.jsx @@ -1,10 +1,47 @@ -import { useEffect, useState, useMemo } from 'react' +import { useEffect, useState, useMemo, useRef } from 'react' import api from '../api/client' import StatusBadge from '../components/StatusBadge' import AgentPanel from '../components/AgentPanel' -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: 'Version', default: true }, + { id: 'wanip', label: 'WAN IP', default: true }, + { id: 'lanip', label: 'LAN IP', default: false }, + { 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: 'lastresponse', label: 'Letzter Kontakt', default: false }, + { id: 'gateways', label: 'Gateways', default: true }, + { id: 'agentversion', label: 'Agent Version', default: false }, +] + +const STORAGE_KEY = 'rmm-firewall-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)) +} + +// Extract short version: "OPNsense 26.1.2_5 (amd64)" -> "26.1.2_5" +function shortVersion(v) { + if (!v) return '' + const m = v.match(/(\d+\.\d+[\w._-]*)/) + return m ? m[1] : v +} + export default function Agents() { const [agents, setAgents] = useState([]) const [customers, setCustomers] = useState([]) @@ -15,6 +52,9 @@ export default function Agents() { 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 +99,25 @@ export default function 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 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] @@ -77,6 +136,14 @@ export default function Agents() { : null const gateways = sys?.gateways || [] + // WAN IP: prefer gateway with WAN role, fallback to interface with WAN role, fallback to a.ip + const ifaces = sys?.network_interfaces || [] + const wanIface = ifaces.find(i => (i.role || '').toUpperCase() === 'WAN') + const lanIface = ifaces.find(i => (i.role || '').toUpperCase() === 'LAN') + const wanGw = gateways.find(g => (g.name || '').toUpperCase().includes('WAN')) + const wanIp = wanIface?.ip || wanGw?.address || a.ip || '' + const lanIp = lanIface?.ip || '' + return { ...a, customer: cust, @@ -86,11 +153,13 @@ export default function Agents() { ramPct, diskPct, uptime: sys?.uptime_seconds || 0, - version: sys?.opnsense_version || a.opnsense_version || '', + version: shortVersion(sys?.opnsense_version || a.opnsense_version || ''), gateways, + wanIp, + lanIp, sys, } - }).filter(Boolean) // Entferne null Werte + }).filter(Boolean) }, [agents, agentDetails, customerMap]) const filtered = useMemo(() => { @@ -163,6 +232,35 @@ export default function Agents() { 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" /> +