////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: /Data/WebUI/src/Device_List.jsx import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Paper, Box, Typography, Table, TableBody, TableCell, TableHead, TableRow, TableSortLabel, IconButton, Menu, MenuItem } from "@mui/material"; import MoreVertIcon from "@mui/icons-material/MoreVert"; import { DeleteDeviceDialog } from "./Dialogs.jsx"; function timeSince(tsSec) { if (!tsSec) return "unknown"; const now = Date.now() / 1000; const s = Math.max(0, Math.floor(now - tsSec)); if (s < 60) return `${s}s`; const m = Math.floor(s / 60); if (m < 60) return `${m}m ${s % 60}s`; const h = Math.floor(m / 60); return `${h}h ${m % 60}m`; } function statusFromHeartbeat(tsSec, offlineAfter = 15) { if (!tsSec) return "Offline"; const now = Date.now() / 1000; return now - tsSec <= offlineAfter ? "Online" : "Offline"; } export default function DeviceList() { const [rows, setRows] = useState([]); const [orderBy, setOrderBy] = useState("status"); const [order, setOrder] = useState("desc"); const [menuAnchor, setMenuAnchor] = useState(null); const [selected, setSelected] = useState(null); const [confirmOpen, setConfirmOpen] = useState(false); const fetchAgents = useCallback(async () => { try { const res = await fetch("/api/agents"); const data = await res.json(); const arr = Object.entries(data || {}).map(([id, a]) => ({ id, hostname: a.hostname || id || "unknown", status: statusFromHeartbeat(a.last_seen), lastSeen: a.last_seen || 0, os: a.agent_operating_system || a.os || "-" })); setRows(arr); } catch (e) { console.warn("Failed to load agents:", e); setRows([]); } }, []); useEffect(() => { fetchAgents(); const t = setInterval(fetchAgents, 5000); return () => clearInterval(t); }, [fetchAgents]); const sorted = useMemo(() => { const dir = order === "asc" ? 1 : -1; return [...rows].sort((a, b) => { const A = a[orderBy]; const B = b[orderBy]; if (orderBy === "lastSeen") return (A - B) * dir; return String(A).localeCompare(String(B)) * dir; }); }, [rows, orderBy, order]); const handleSort = (col) => { if (orderBy === col) setOrder(order === "asc" ? "desc" : "asc"); else { setOrderBy(col); setOrder("asc"); } }; const statusColor = (s) => (s === "Online" ? "#00d18c" : "#ff4f4f"); const openMenu = (e, row) => { setMenuAnchor(e.currentTarget); setSelected(row); }; const closeMenu = () => setMenuAnchor(null); const confirmDelete = () => { closeMenu(); setConfirmOpen(true); }; const handleDelete = async () => { if (!selected) return; try { await fetch(`/api/agent/${selected.id}`, { method: "DELETE" }); } catch (e) { console.warn("Failed to remove agent", e); } setRows((r) => r.filter((x) => x.id !== selected.id)); setConfirmOpen(false); setSelected(null); }; return ( Devices Devices connected to Borealis via Agent and their recent heartbeats. handleSort("status")} > Status handleSort("hostname")} > Device Hostname handleSort("lastSeen")} > Last Heartbeat handleSort("os")} > OS {sorted.map((r, i) => ( {r.status} {r.hostname} {timeSince(r.lastSeen)} {r.os} openMenu(e, r)} sx={{ color: "#ccc" }} > ))} {sorted.length === 0 && ( No agents connected. )}
Delete setConfirmOpen(false)} onConfirm={handleDelete} />
); }