Add device activity clear option and menu

This commit is contained in:
2025-09-08 13:41:05 -06:00
parent b5b8137dcc
commit 22c8b0a709
3 changed files with 90 additions and 16 deletions

View File

@@ -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 }) {
</Typography>
</Box>
<Box>
<Button
variant="outlined"
<IconButton
size="small"
disabled={!(agent?.hostname || device?.hostname)}
onClick={() => setQuickJobOpen(true)}
onClick={(e) => setMenuAnchor(e.currentTarget)}
sx={{
color: !(agent?.hostname || device?.hostname) ? "#666" : "#58a6ff",
borderColor: !(agent?.hostname || device?.hostname) ? "#333" : "#58a6ff",
textTransform: "none"
border: "1px solid",
borderRadius: 1,
width: 32,
height: 32
}}
>
Quick Job
</Button>
<MoreHorizIcon fontSize="small" />
</IconButton>
<Menu
anchorEl={menuAnchor}
open={Boolean(menuAnchor)}
onClose={() => setMenuAnchor(null)}
>
<MenuItem
onClick={() => {
setMenuAnchor(null);
setQuickJobOpen(true);
}}
>
Quick Job
</MenuItem>
<MenuItem
onClick={() => {
setMenuAnchor(null);
setClearDialogOpen(true);
}}
>
Clear Device Activity
</MenuItem>
</Menu>
</Box>
</Box>
<Tabs
@@ -751,13 +793,22 @@ export default function DeviceDetails({ device, onBack }) {
</DialogActions>
</Dialog>
{quickJobOpen && (
<QuickJob
open={quickJobOpen}
onClose={() => setQuickJobOpen(false)}
hostnames={[agent?.hostname || device?.hostname].filter(Boolean)}
<ClearDeviceActivityDialog
open={clearDialogOpen}
onCancel={() => setClearDialogOpen(false)}
onConfirm={() => {
clearHistory();
setClearDialogOpen(false);
}}
/>
)}
</Paper>
);
}
{quickJobOpen && (
<QuickJob
open={quickJobOpen}
onClose={() => setQuickJobOpen(false)}
hostnames={[agent?.hostname || device?.hostname].filter(Boolean)}
/>
)}
</Paper>
);
}

View File

@@ -207,6 +207,23 @@ export function NewWorkflowDialog({ open, value, onChange, onCancel, onCreate })
);
}
export function ClearDeviceActivityDialog({ open, onCancel, onConfirm }) {
return (
<Dialog open={open} onClose={onCancel} PaperProps={{ sx: { bgcolor: "#121212", color: "#fff" } }}>
<DialogTitle>Clear Device Activity</DialogTitle>
<DialogContent>
<DialogContentText sx={{ color: "#ccc" }}>
All device activity history will be cleared, are you sure?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onCancel} sx={{ color: "#58a6ff" }}>Cancel</Button>
<Button onClick={onConfirm} sx={{ color: "#ff4f4f" }}>Clear</Button>
</DialogActions>
</Dialog>
);
}
export function SaveWorkflowDialog({ open, value, onChange, onCancel, onSave }) {
return (
<Dialog open={open} onClose={onCancel} PaperProps={{ sx: { bgcolor: "#121212", color: "#fff" } }}>

View File

@@ -1409,11 +1409,17 @@ def scripts_quick_run():
return jsonify({"results": results})
@app.route("/api/device/activity/<hostname>", methods=["GET"])
@app.route("/api/device/activity/<hostname>", 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,),