Add device details component

This commit is contained in:
2025-08-12 21:59:27 -06:00
parent f1fe831911
commit 95e87ad4c3
3 changed files with 171 additions and 3 deletions

View File

@@ -29,6 +29,7 @@ import DeviceList from "./Device_List";
import ScriptList from "./Script_List"; import ScriptList from "./Script_List";
import ScheduledJobsList from "./Scheduled_Jobs_List"; import ScheduledJobsList from "./Scheduled_Jobs_List";
import Login from "./Login.jsx"; import Login from "./Login.jsx";
import DeviceDetails from "./Device_Details";
import { io } from "socket.io-client"; import { io } from "socket.io-client";
@@ -77,6 +78,7 @@ export default function App() {
const [tabs, setTabs] = useState([{ id: "flow_1", tab_name: "Flow 1", nodes: [], edges: [] }]); const [tabs, setTabs] = useState([{ id: "flow_1", tab_name: "Flow 1", nodes: [], edges: [] }]);
const [activeTabId, setActiveTabId] = useState("flow_1"); const [activeTabId, setActiveTabId] = useState("flow_1");
const [currentPage, setCurrentPage] = useState("devices"); const [currentPage, setCurrentPage] = useState("devices");
const [selectedDevice, setSelectedDevice] = useState(null);
const [aboutAnchorEl, setAboutAnchorEl] = useState(null); const [aboutAnchorEl, setAboutAnchorEl] = useState(null);
const [creditsDialogOpen, setCreditsDialogOpen] = useState(false); const [creditsDialogOpen, setCreditsDialogOpen] = useState(false);
@@ -288,7 +290,25 @@ export default function App() {
const renderMainContent = () => { const renderMainContent = () => {
switch (currentPage) { switch (currentPage) {
case "devices": case "devices":
return <DeviceList />; return (
<DeviceList
onSelectDevice={(d) => {
setSelectedDevice(d);
setCurrentPage("device_details");
}}
/>
);
case "device_details":
return (
<DeviceDetails
device={selectedDevice}
onBack={() => {
setCurrentPage("devices");
setSelectedDevice(null);
}}
/>
);
case "jobs": case "jobs":
return <ScheduledJobsList />; return <ScheduledJobsList />;

View File

@@ -0,0 +1,143 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/Device_Details.jsx
import React, { useState, useEffect } from "react";
import {
Paper,
Box,
Tabs,
Tab,
Typography,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
Button
} from "@mui/material";
export default function DeviceDetails({ device, onBack }) {
const [tab, setTab] = useState(0);
const [agent, setAgent] = useState(device || {});
useEffect(() => {
if (!device || !device.id) return;
const fetchAgent = async () => {
try {
const res = await fetch("/api/agents");
const data = await res.json();
if (data && data[device.id]) {
setAgent({ id: device.id, ...data[device.id] });
}
} catch (e) {
console.warn("Failed to load agent", e);
}
};
fetchAgent();
}, [device]);
const summaryItems = [
{ label: "Device Name", value: agent.hostname || device?.hostname || "unknown" },
{ label: "Description", value: "unknown" },
{ label: "Operating System", value: agent.agent_operating_system || "unknown" },
{ label: "Last User", value: "unknown" },
{ label: "Internal IP", value: agent.internal_ip || "unknown" },
{ label: "External IP", value: "unknown" },
{ label: "Last Reboot", value: agent.last_reboot || "unknown" },
{ label: "Created", value: agent.created || "unknown" }
];
const renderSummary = () => (
<Table size="small">
<TableBody>
{summaryItems.map((item) => (
<TableRow key={item.label}>
<TableCell sx={{ fontWeight: 500 }}>{item.label}</TableCell>
<TableCell>{item.value}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
const placeholderTable = (headers) => (
<Table size="small">
<TableHead>
<TableRow>
{headers.map((h) => (
<TableCell key={h}>{h}</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell colSpan={headers.length} sx={{ color: "#888" }}>
No data available.
</TableCell>
</TableRow>
</TableBody>
</Table>
);
const tabs = [
{ label: "Summary", content: renderSummary() },
{
label: "Monitors",
content: placeholderTable([
"Type",
"Description",
"Latest Value",
"Policy",
"Latest 10 Days of Alerts",
"Enabled/Disabled Status"
])
},
{
label: "Software",
content: placeholderTable(["Software Name", "Version", "Action"])
},
{
label: "Memory",
content: placeholderTable(["Slot", "Speed", "Serial Number", "Capacity"])
},
{
label: "Storage",
content: placeholderTable([
"Drive Letter",
"Disk Type",
"Usage",
"Total Size",
"Free %"
])
},
{
label: "Network",
content: placeholderTable(["Adapter", "IP Address", "MAC Address"])
}
];
return (
<Paper sx={{ m: 2, p: 2, bgcolor: "#1e1e1e" }} elevation={2}>
<Box sx={{ mb: 2, display: "flex", alignItems: "center" }}>
{onBack && (
<Button variant="outlined" size="small" onClick={onBack} sx={{ mr: 2 }}>
Back
</Button>
)}
<Typography variant="h6" sx={{ color: "#58a6ff" }}>
{agent.hostname || "Device Details"}
</Typography>
</Box>
<Tabs
value={tab}
onChange={(e, v) => setTab(v)}
sx={{ borderBottom: 1, borderColor: "#333" }}
>
{tabs.map((t) => (
<Tab key={t.label} label={t.label} />
))}
</Tabs>
<Box sx={{ mt: 2 }}>{tabs[tab].content}</Box>
</Paper>
);
}

View File

@@ -35,7 +35,7 @@ function statusFromHeartbeat(tsSec, offlineAfter = 15) {
return now - tsSec <= offlineAfter ? "Online" : "Offline"; return now - tsSec <= offlineAfter ? "Online" : "Offline";
} }
export default function DeviceList() { export default function DeviceList({ onSelectDevice }) {
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
const [orderBy, setOrderBy] = useState("status"); const [orderBy, setOrderBy] = useState("status");
const [order, setOrder] = useState("desc"); const [order, setOrder] = useState("desc");
@@ -165,7 +165,12 @@ export default function DeviceList() {
</TableHead> </TableHead>
<TableBody> <TableBody>
{sorted.map((r, i) => ( {sorted.map((r, i) => (
<TableRow key={r.id || i} hover> <TableRow
key={r.id || i}
hover
onClick={() => onSelectDevice && onSelectDevice(r)}
sx={{ cursor: onSelectDevice ? "pointer" : "default" }}
>
<TableCell> <TableCell>
<span <span
style={{ style={{