diff --git a/Data/Server/WebUI/src/Device_Details.jsx b/Data/Server/WebUI/src/Device_Details.jsx index 6c61e2b..32aa907 100644 --- a/Data/Server/WebUI/src/Device_Details.jsx +++ b/Data/Server/WebUI/src/Device_Details.jsx @@ -283,25 +283,40 @@ export default function DeviceDetails({ device, onBack }) { }; const renderStorage = () => { - const parseNum = (val) => { + const toNum = (val) => { 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; }; + const rows = (details.storage || []).map((d) => { - const total = parseNum(d.total); - const rawFree = parseNum(d.free); - const freePct = rawFree !== undefined - ? rawFree <= 100 - ? rawFree - : total - ? (rawFree / total) * 100 - : undefined - : undefined; - let usage = parseNum(d.usage); - if ((usage === undefined || Number.isNaN(usage)) && freePct !== undefined) { - usage = 100 - freePct; + const total = toNum(d.total); + let usage = toNum(d.usage); + let freePct; + + if (usage !== undefined) { + if (usage <= 1) usage *= 100; + freePct = 100 - usage; + } else { + const freeRaw = toNum(d.free); + if (freeRaw !== undefined) { + if (freeRaw > 1 && freeRaw > 100 && total) { + 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 { drive: d.drive, disk_type: d.disk_type, @@ -310,8 +325,10 @@ export default function DeviceDetails({ device, onBack }) { free: freePct, }; }); + if (!rows.length) return placeholderTable(["Drive Letter", "Disk Type", "Usage", "Total Size", "Free %"]); + return ( diff --git a/Data/Server/WebUI/src/Device_List.jsx b/Data/Server/WebUI/src/Device_List.jsx index adf87c7..07984d8 100644 --- a/Data/Server/WebUI/src/Device_List.jsx +++ b/Data/Server/WebUI/src/Device_List.jsx @@ -165,12 +165,7 @@ export default function DeviceList({ onSelectDevice }) { {sorted.map((r, i) => ( - onSelectDevice && onSelectDevice(r)} - sx={{ cursor: onSelectDevice ? "pointer" : "default" }} - > + - {r.hostname} + onSelectDevice && onSelectDevice(r)} + sx={{ cursor: onSelectDevice ? "pointer" : "default" }} + > + {r.hostname} + {timeSince(r.lastSeen)} {r.os} openMenu(e, r)} + onClick={(e) => { + e.stopPropagation(); + openMenu(e, r); + }} sx={{ color: "#ccc" }} > diff --git a/Data/Server/server.py b/Data/Server/server.py index 9738f97..6ac27c1 100644 --- a/Data/Server/server.py +++ b/Data/Server/server.py @@ -398,6 +398,36 @@ def 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") def get_agents(): """ @@ -481,10 +511,20 @@ def set_device_description(hostname: str): @app.route("/api/agent/", methods=["DELETE"]) def delete_agent(agent_id: str): - """Remove an agent from the in-memory registry.""" - if agent_id in registered_agents: - registered_agents.pop(agent_id, None) - agent_configurations.pop(agent_id, None) + """Remove an agent from the registry and database.""" + info = registered_agents.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({"error": "agent not found"}), 404