mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-09-11 02:08:44 -06:00
Merge pull request #32 from bunny-lab-io:codex/adjust-quick-job-button-to-ellipsis-menu
Add device activity clearing dialog and menu
This commit is contained in:
@@ -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
|
||||
}}
|
||||
>
|
||||
<MoreHorizIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={menuAnchor}
|
||||
open={Boolean(menuAnchor)}
|
||||
onClose={() => setMenuAnchor(null)}
|
||||
>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setMenuAnchor(null);
|
||||
setQuickJobOpen(true);
|
||||
}}
|
||||
>
|
||||
Quick Job
|
||||
</Button>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setMenuAnchor(null);
|
||||
setClearDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
Clear Device Activity
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Box>
|
||||
</Box>
|
||||
<Tabs
|
||||
@@ -751,6 +793,15 @@ export default function DeviceDetails({ device, onBack }) {
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<ClearDeviceActivityDialog
|
||||
open={clearDialogOpen}
|
||||
onCancel={() => setClearDialogOpen(false)}
|
||||
onConfirm={() => {
|
||||
clearHistory();
|
||||
setClearDialogOpen(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
{quickJobOpen && (
|
||||
<QuickJob
|
||||
open={quickJobOpen}
|
||||
@@ -760,4 +811,4 @@ export default function DeviceDetails({ device, onBack }) {
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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" } }}>
|
||||
|
@@ -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,),
|
||||
|
Reference in New Issue
Block a user