From 22c8b0a7095250762a96c89dc26940f51a55e439 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Mon, 8 Sep 2025 13:41:05 -0600 Subject: [PATCH] Add device activity clear option and menu --- .../WebUI/src/Devices/Device_Details.jsx | 81 +++++++++++++++---- Data/Server/WebUI/src/Dialogs.jsx | 17 ++++ Data/Server/server.py | 8 +- 3 files changed, 90 insertions(+), 16 deletions(-) diff --git a/Data/Server/WebUI/src/Devices/Device_Details.jsx b/Data/Server/WebUI/src/Devices/Device_Details.jsx index b103856..115c00f 100644 --- a/Data/Server/WebUI/src/Devices/Device_Details.jsx +++ b/Data/Server/WebUI/src/Devices/Device_Details.jsx @@ -13,6 +13,9 @@ import { TableCell, TableBody, Button, + IconButton, + Menu, + MenuItem, LinearProgress, TableSortLabel, TextField, @@ -21,6 +24,8 @@ import { DialogContent, DialogActions } from "@mui/material"; +import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; +import { ClearDeviceActivityDialog } from "../Dialogs.jsx"; import Prism from "prismjs"; import "prismjs/components/prism-yaml"; import "prismjs/components/prism-bash"; @@ -46,6 +51,8 @@ export default function DeviceDetails({ device, onBack }) { const [outputContent, setOutputContent] = useState(""); const [outputLang, setOutputLang] = useState("powershell"); const [quickJobOpen, setQuickJobOpen] = useState(false); + const [menuAnchor, setMenuAnchor] = useState(null); + const [clearDialogOpen, setClearDialogOpen] = useState(false); // Snapshotted status for the lifetime of this page const [lockedStatus, setLockedStatus] = useState(() => { // Prefer status provided by the device list row if available @@ -124,6 +131,17 @@ export default function DeviceDetails({ device, onBack }) { useEffect(() => { loadHistory(); }, [loadHistory]); + const clearHistory = async () => { + if (!device?.hostname) return; + try { + const resp = await fetch(`/api/device/activity/${encodeURIComponent(device.hostname)}`, { method: "DELETE" }); + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + setHistoryRows([]); + } catch (e) { + console.warn("Failed to clear activity history", e); + } + }; + const saveDescription = async () => { if (!details.summary?.hostname) return; try { @@ -699,19 +717,43 @@ export default function DeviceDetails({ device, onBack }) { - + + + setMenuAnchor(null)} + > + { + setMenuAnchor(null); + setQuickJobOpen(true); + }} + > + Quick Job + + { + setMenuAnchor(null); + setClearDialogOpen(true); + }} + > + Clear Device Activity + + - {quickJobOpen && ( - setQuickJobOpen(false)} - hostnames={[agent?.hostname || device?.hostname].filter(Boolean)} + setClearDialogOpen(false)} + onConfirm={() => { + clearHistory(); + setClearDialogOpen(false); + }} /> - )} - - ); -} + + {quickJobOpen && ( + setQuickJobOpen(false)} + hostnames={[agent?.hostname || device?.hostname].filter(Boolean)} + /> + )} + + ); + } diff --git a/Data/Server/WebUI/src/Dialogs.jsx b/Data/Server/WebUI/src/Dialogs.jsx index 1aab8e0..49aed0b 100644 --- a/Data/Server/WebUI/src/Dialogs.jsx +++ b/Data/Server/WebUI/src/Dialogs.jsx @@ -207,6 +207,23 @@ export function NewWorkflowDialog({ open, value, onChange, onCancel, onCreate }) ); } +export function ClearDeviceActivityDialog({ open, onCancel, onConfirm }) { + return ( + + Clear Device Activity + + + All device activity history will be cleared, are you sure? + + + + + + + + ); +} + export function SaveWorkflowDialog({ open, value, onChange, onCancel, onSave }) { return ( diff --git a/Data/Server/server.py b/Data/Server/server.py index 1b87287..79d58c1 100644 --- a/Data/Server/server.py +++ b/Data/Server/server.py @@ -1409,11 +1409,17 @@ def scripts_quick_run(): return jsonify({"results": results}) -@app.route("/api/device/activity/", methods=["GET"]) +@app.route("/api/device/activity/", methods=["GET", "DELETE"]) def device_activity(hostname: str): try: conn = sqlite3.connect(DB_PATH) cur = conn.cursor() + if request.method == "DELETE": + cur.execute("DELETE FROM activity_history WHERE hostname = ?", (hostname,)) + conn.commit() + conn.close() + return jsonify({"status": "ok"}) + cur.execute( "SELECT id, script_name, script_path, script_type, ran_at, status, LENGTH(stdout), LENGTH(stderr) FROM activity_history WHERE hostname = ? ORDER BY ran_at DESC, id DESC", (hostname,),