mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-09-10 23:08:43 -06:00
Add device activity clear option and menu
This commit is contained in:
@@ -13,6 +13,9 @@ import {
|
|||||||
TableCell,
|
TableCell,
|
||||||
TableBody,
|
TableBody,
|
||||||
Button,
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
TableSortLabel,
|
TableSortLabel,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -21,6 +24,8 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogActions
|
DialogActions
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
|
||||||
|
import { ClearDeviceActivityDialog } from "../Dialogs.jsx";
|
||||||
import Prism from "prismjs";
|
import Prism from "prismjs";
|
||||||
import "prismjs/components/prism-yaml";
|
import "prismjs/components/prism-yaml";
|
||||||
import "prismjs/components/prism-bash";
|
import "prismjs/components/prism-bash";
|
||||||
@@ -46,6 +51,8 @@ export default function DeviceDetails({ device, onBack }) {
|
|||||||
const [outputContent, setOutputContent] = useState("");
|
const [outputContent, setOutputContent] = useState("");
|
||||||
const [outputLang, setOutputLang] = useState("powershell");
|
const [outputLang, setOutputLang] = useState("powershell");
|
||||||
const [quickJobOpen, setQuickJobOpen] = useState(false);
|
const [quickJobOpen, setQuickJobOpen] = useState(false);
|
||||||
|
const [menuAnchor, setMenuAnchor] = useState(null);
|
||||||
|
const [clearDialogOpen, setClearDialogOpen] = useState(false);
|
||||||
// Snapshotted status for the lifetime of this page
|
// Snapshotted status for the lifetime of this page
|
||||||
const [lockedStatus, setLockedStatus] = useState(() => {
|
const [lockedStatus, setLockedStatus] = useState(() => {
|
||||||
// Prefer status provided by the device list row if available
|
// Prefer status provided by the device list row if available
|
||||||
@@ -124,6 +131,17 @@ export default function DeviceDetails({ device, onBack }) {
|
|||||||
|
|
||||||
useEffect(() => { loadHistory(); }, [loadHistory]);
|
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 () => {
|
const saveDescription = async () => {
|
||||||
if (!details.summary?.hostname) return;
|
if (!details.summary?.hostname) return;
|
||||||
try {
|
try {
|
||||||
@@ -699,19 +717,43 @@ export default function DeviceDetails({ device, onBack }) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Button
|
<IconButton
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
size="small"
|
||||||
disabled={!(agent?.hostname || device?.hostname)}
|
disabled={!(agent?.hostname || device?.hostname)}
|
||||||
onClick={() => setQuickJobOpen(true)}
|
onClick={(e) => setMenuAnchor(e.currentTarget)}
|
||||||
sx={{
|
sx={{
|
||||||
color: !(agent?.hostname || device?.hostname) ? "#666" : "#58a6ff",
|
color: !(agent?.hostname || device?.hostname) ? "#666" : "#58a6ff",
|
||||||
borderColor: !(agent?.hostname || device?.hostname) ? "#333" : "#58a6ff",
|
borderColor: !(agent?.hostname || device?.hostname) ? "#333" : "#58a6ff",
|
||||||
textTransform: "none"
|
border: "1px solid",
|
||||||
|
borderRadius: 1,
|
||||||
|
width: 32,
|
||||||
|
height: 32
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Quick Job
|
<MoreHorizIcon fontSize="small" />
|
||||||
</Button>
|
</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>
|
||||||
</Box>
|
</Box>
|
||||||
<Tabs
|
<Tabs
|
||||||
@@ -751,13 +793,22 @@ export default function DeviceDetails({ device, onBack }) {
|
|||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{quickJobOpen && (
|
<ClearDeviceActivityDialog
|
||||||
<QuickJob
|
open={clearDialogOpen}
|
||||||
open={quickJobOpen}
|
onCancel={() => setClearDialogOpen(false)}
|
||||||
onClose={() => setQuickJobOpen(false)}
|
onConfirm={() => {
|
||||||
hostnames={[agent?.hostname || device?.hostname].filter(Boolean)}
|
clearHistory();
|
||||||
|
setClearDialogOpen(false);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Paper>
|
{quickJobOpen && (
|
||||||
);
|
<QuickJob
|
||||||
}
|
open={quickJobOpen}
|
||||||
|
onClose={() => setQuickJobOpen(false)}
|
||||||
|
hostnames={[agent?.hostname || device?.hostname].filter(Boolean)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</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 }) {
|
export function SaveWorkflowDialog({ open, value, onChange, onCancel, onSave }) {
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onCancel} PaperProps={{ sx: { bgcolor: "#121212", color: "#fff" } }}>
|
<Dialog open={open} onClose={onCancel} PaperProps={{ sx: { bgcolor: "#121212", color: "#fff" } }}>
|
||||||
|
@@ -1409,11 +1409,17 @@ def scripts_quick_run():
|
|||||||
return jsonify({"results": results})
|
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):
|
def device_activity(hostname: str):
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(DB_PATH)
|
conn = sqlite3.connect(DB_PATH)
|
||||||
cur = conn.cursor()
|
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(
|
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",
|
"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,),
|
(hostname,),
|
||||||
|
Reference in New Issue
Block a user