diff --git a/frontend/src/pages/AssetsPage.jsx b/frontend/src/pages/AssetsPage.jsx index 5916dd0..b4631e7 100644 --- a/frontend/src/pages/AssetsPage.jsx +++ b/frontend/src/pages/AssetsPage.jsx @@ -241,7 +241,7 @@ const AssignModal = ({ asset, onClose, onAssigned }) => { useEffect(() => { authFetch(`${API}/users`) .then(r => r.json()) - .then(data => setUsers(Array.isArray(data) ? data : (data.users || []))) + .then(data => setUsers(data.data || data.users || (Array.isArray(data) ? data : []))) .catch(() => {}); }, []); @@ -449,7 +449,7 @@ const AssetsPage = () => { if (!detail) return; if (!window.confirm('Zuweisung wirklich aufheben?')) return; try { - const res = await authFetch(`${API}/assets/${detail.id}/unassign`, { method: 'POST' }); + const res = await authFetch(`${API}/assets/${detail.id}/unassign`, { method: 'POST', body: JSON.stringify({ new_status: 'verfuegbar' }) }); if (!res.ok) throw new Error(await res.text()); toast.success('Zuweisung aufgehoben'); loadAssets(); @@ -1024,6 +1024,72 @@ const AssetsPage = () => { ) : null) ) :
Kein Agent verbunden
} + {/* Auslastung */} +
+
πŸ“Š Auslastung
+ {agent ? (() => { + const cpuPct = agent.cpu_usage_percent ?? null; + const ramUsed = agent.ram_used_gb ?? null; + const ramTotal = agent.ram_total_gb ?? null; + const ramPct = ramTotal > 0 ? Math.round((ramUsed / ramTotal) * 100) : null; + const diskTotal = agent.disk_total_gb ?? null; + const diskFree = agent.disk_free_gb ?? null; + const diskUsed = diskTotal != null && diskFree != null ? diskTotal - diskFree : null; + const diskPct = diskTotal > 0 ? Math.round((diskUsed / diskTotal) * 100) : null; + const uptimeH = agent.uptime_hours ?? null; + const fmtUptime = (h) => { + if (h == null) return 'β€”'; + if (h < 24) return `${Math.floor(h)} Std.`; + return `${Math.floor(h / 24)} Tag(e)`; + }; + const barColor = (p) => p > 80 ? '#ef4444' : p > 60 ? '#f59e0b' : '#34d399'; + const Bar = ({ pct: p }) => ( +
+
+
+ ); + return ( + <> + {cpuPct != null && ( +
+
+ CPU-Auslastung + {cpuPct.toFixed(1)} % +
+ +
+ )} + {ramUsed != null && ramTotal != null && ( +
+
+ RAM + {ramUsed.toFixed(1)} / {ramTotal.toFixed(0)} GB +
+ +
+ )} + {diskUsed != null && diskTotal != null && ( +
+
+ Disk + {diskUsed.toFixed(0)} / {diskTotal.toFixed(0)} GB +
+ +
+ )} + {uptimeH != null && ( +
+ Laufzeit + {fmtUptime(uptimeH)} +
+ )} + {cpuPct == null && ramUsed == null && diskUsed == null && uptimeH == null && ( +
Keine Auslastungsdaten
+ )} + + ); + })() :
Kein Agent verbunden
} +
)} @@ -1031,14 +1097,27 @@ const AssetsPage = () => { {activeTab === 'dokumente' && (
πŸ“ Dokumente & Aktionen
-
window.open(`${API}/assets/${detail.id}/label`, '_blank')} - > -
🏷️
-
-
Asset-Label drucken
-
QR-Code Label als PDF ΓΆffnen
+
+
+
🏷️
+
+
Asset-Label drucken
+
Format wΓ€hlen und als PDF ΓΆffnen
+
+
+
+ {[ + { key: 'small', label: 'Klein (50Γ—25mm)' }, + { key: 'medium', label: 'Mittel (80Γ—40mm)' }, + { key: 'large', label: 'Groß (A5)' }, + { key: 'ptouch12', label: 'P-Touch 700 (12mm)' }, + ].map(f => ( + + ))}
{detail.status === 'zugewiesen' && ( diff --git a/frontend/src/pages/MonitoringPage.jsx b/frontend/src/pages/MonitoringPage.jsx index 4c005ea..27e52e0 100644 --- a/frontend/src/pages/MonitoringPage.jsx +++ b/frontend/src/pages/MonitoringPage.jsx @@ -748,6 +748,7 @@ const MonitoringPage = () => { const [agentFilter, setAgentFilter] = useState('all'); const [expandedAgents, setExpandedAgents] = useState(new Set()); const [expandedHosts, setExpandedHosts] = useState(new Set()); + const [agentSort, setAgentSort] = useState('cpu'); const [hostFilter, setHostFilter] = useState('all'); const [hostSearch, setHostSearch] = useState(''); const [proxmoxData, setProxmoxData] = useState(null); @@ -1186,6 +1187,11 @@ const MonitoringPage = () => { // ─── Tab: Agents ────────────────────────────────────────────────────────── const renderAgents = () => { + const isAgentOffline = (a) => { + if (!a.last_checkin) return true; + return Date.now() - new Date(a.last_checkin).getTime() > 5 * 60 * 1000; + }; + const filtered = agents.filter(a => { if (agentSearch && !a.hostname.toLowerCase().includes(agentSearch.toLowerCase()) && !(a.ip_address || '').includes(agentSearch)) return false; if (agentFilter === 'online' && a.status !== 'online') return false; @@ -1193,6 +1199,27 @@ const MonitoringPage = () => { if (agentFilter === 'warn' && warnings(a).length === 0) return false; return true; }); + + const sorted = [...filtered].sort((a, b) => { + const aOffline = isAgentOffline(a); + const bOffline = isAgentOffline(b); + // Offline immer ans Ende + if (aOffline !== bOffline) return aOffline ? 1 : -1; + if (agentSort === 'cpu') { + const av = a.cpu_usage_percent ?? (a.ram_total_gb > 0 ? (a.ram_used_gb / a.ram_total_gb) * 100 : 0); + const bv = b.cpu_usage_percent ?? (b.ram_total_gb > 0 ? (b.ram_used_gb / b.ram_total_gb) * 100 : 0); + return bv - av; + } + if (agentSort === 'ram') { + const av = a.ram_total_gb > 0 ? (a.ram_used_gb / a.ram_total_gb) * 100 : 0; + const bv = b.ram_total_gb > 0 ? (b.ram_used_gb / b.ram_total_gb) * 100 : 0; + return bv - av; + } + if (agentSort === 'name') return (a.hostname || '').localeCompare(b.hostname || ''); + if (agentSort === 'seen') return new Date(b.last_checkin || 0) - new Date(a.last_checkin || 0); + return 0; + }); + return ( <>
@@ -1203,10 +1230,16 @@ const MonitoringPage = () => { {pill(agentFilter === 'offline', () => setAgentFilter('offline'), 'OFFLINE')} {pill(agentFilter === 'warn', () => setAgentFilter('warn'), 'WARNUNG')}
- {filtered.length} EintrΓ€ge + + {sorted.length} EintrΓ€ge
- {filtered.length === 0 + {sorted.length === 0 ?
Keine Agents gefunden
: ( /* Checkmk-style service table */ @@ -1221,7 +1254,7 @@ const MonitoringPage = () => { - {filtered.map(a => { + {sorted.map(a => { const w = warnings(a), hasErr = w.some(x => x.type === 'error'); const state = a.status === 'offline' ? 'crit' : hasErr ? 'crit' : w.length ? 'warn' : 'ok'; const isExp = expandedAgents.has(a.id);