Load device records on startup and improve storage display

This commit is contained in:
2025-08-13 01:50:08 -06:00
parent 7a48fc2a28
commit 7f5e0c69da
3 changed files with 86 additions and 26 deletions

View File

@@ -283,25 +283,40 @@ export default function DeviceDetails({ device, onBack }) {
}; };
const renderStorage = () => { const renderStorage = () => {
const parseNum = (val) => { const toNum = (val) => {
if (val === undefined || val === null) return undefined; if (val === undefined || val === null) return undefined;
const n = Number(String(val).replace(/[^0-9.]/g, "")); const n = Number(val);
return Number.isNaN(n) ? undefined : n; return Number.isNaN(n) ? undefined : n;
}; };
const rows = (details.storage || []).map((d) => { const rows = (details.storage || []).map((d) => {
const total = parseNum(d.total); const total = toNum(d.total);
const rawFree = parseNum(d.free); let usage = toNum(d.usage);
const freePct = rawFree !== undefined let freePct;
? rawFree <= 100
? rawFree if (usage !== undefined) {
: total if (usage <= 1) usage *= 100;
? (rawFree / total) * 100 freePct = 100 - usage;
: undefined } else {
: undefined; const freeRaw = toNum(d.free);
let usage = parseNum(d.usage); if (freeRaw !== undefined) {
if ((usage === undefined || Number.isNaN(usage)) && freePct !== undefined) { if (freeRaw > 1 && freeRaw > 100 && total) {
usage = 100 - freePct; freePct = (freeRaw / total) * 100;
} else if (freeRaw <= 1) {
freePct = freeRaw * 100;
} else {
freePct = freeRaw;
}
usage = freePct !== undefined ? 100 - freePct : undefined;
} else {
const usedRaw = toNum(d.used);
if (usedRaw !== undefined && total) {
usage = (usedRaw / total) * 100;
freePct = 100 - usage;
}
}
} }
return { return {
drive: d.drive, drive: d.drive,
disk_type: d.disk_type, disk_type: d.disk_type,
@@ -310,8 +325,10 @@ export default function DeviceDetails({ device, onBack }) {
free: freePct, free: freePct,
}; };
}); });
if (!rows.length) if (!rows.length)
return placeholderTable(["Drive Letter", "Disk Type", "Usage", "Total Size", "Free %"]); return placeholderTable(["Drive Letter", "Disk Type", "Usage", "Total Size", "Free %"]);
return ( return (
<Box sx={{ maxHeight: 400, overflowY: "auto" }}> <Box sx={{ maxHeight: 400, overflowY: "auto" }}>
<Table size="small"> <Table size="small">

View File

@@ -165,12 +165,7 @@ export default function DeviceList({ onSelectDevice }) {
</TableHead> </TableHead>
<TableBody> <TableBody>
{sorted.map((r, i) => ( {sorted.map((r, i) => (
<TableRow <TableRow key={r.id || i} hover>
key={r.id || i}
hover
onClick={() => onSelectDevice && onSelectDevice(r)}
sx={{ cursor: onSelectDevice ? "pointer" : "default" }}
>
<TableCell> <TableCell>
<Box sx={{ display: "flex", alignItems: "center" }}> <Box sx={{ display: "flex", alignItems: "center" }}>
<Box <Box
@@ -187,13 +182,21 @@ export default function DeviceList({ onSelectDevice }) {
{r.status} {r.status}
</Box> </Box>
</TableCell> </TableCell>
<TableCell>{r.hostname}</TableCell> <TableCell
onClick={() => onSelectDevice && onSelectDevice(r)}
sx={{ cursor: onSelectDevice ? "pointer" : "default" }}
>
{r.hostname}
</TableCell>
<TableCell>{timeSince(r.lastSeen)}</TableCell> <TableCell>{timeSince(r.lastSeen)}</TableCell>
<TableCell>{r.os}</TableCell> <TableCell>{r.os}</TableCell>
<TableCell align="right"> <TableCell align="right">
<IconButton <IconButton
size="small" size="small"
onClick={(e) => openMenu(e, r)} onClick={(e) => {
e.stopPropagation();
openMenu(e, r);
}}
sx={{ color: "#ccc" }} sx={{ color: "#ccc" }}
> >
<MoreVertIcon fontSize="small" /> <MoreVertIcon fontSize="small" />

View File

@@ -398,6 +398,36 @@ def init_db():
init_db() init_db()
def load_agents_from_db():
"""Populate registered_agents with any devices stored in the database."""
try:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT hostname, details FROM device_details")
for hostname, details_json in cur.fetchall():
try:
details = json.loads(details_json or "{}")
except Exception:
details = {}
summary = details.get("summary", {})
agent_id = summary.get("agent_id") or hostname
registered_agents[agent_id] = {
"agent_id": agent_id,
"hostname": summary.get("hostname") or hostname,
"agent_operating_system": summary.get("operating_system")
or summary.get("agent_operating_system")
or "-",
"last_seen": summary.get("last_seen") or 0,
"status": "Offline",
}
conn.close()
except Exception as e:
print(f"[WARN] Failed to load agents from DB: {e}")
load_agents_from_db()
@app.route("/api/agents") @app.route("/api/agents")
def get_agents(): def get_agents():
""" """
@@ -481,10 +511,20 @@ def set_device_description(hostname: str):
@app.route("/api/agent/<agent_id>", methods=["DELETE"]) @app.route("/api/agent/<agent_id>", methods=["DELETE"])
def delete_agent(agent_id: str): def delete_agent(agent_id: str):
"""Remove an agent from the in-memory registry.""" """Remove an agent from the registry and database."""
if agent_id in registered_agents: info = registered_agents.pop(agent_id, None)
registered_agents.pop(agent_id, None) agent_configurations.pop(agent_id, None)
agent_configurations.pop(agent_id, None) hostname = info.get("hostname") if info else None
if hostname:
try:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("DELETE FROM device_details WHERE hostname = ?", (hostname,))
conn.commit()
conn.close()
except Exception as e:
return jsonify({"error": str(e)}), 500
if info:
return jsonify({"status": "removed"}) return jsonify({"status": "removed"})
return jsonify({"error": "agent not found"}), 404 return jsonify({"error": "agent not found"}), 404