setSelectedNodeId(id)}>
- {workflowTree.length ? workflowTree.map((n) => (
+ {
+ const n = workflowMap[id];
+ if (n && !n.isFolder) setSelectedNodeId(id);
+ }}>
+ {workflowTree.length ? (workflowTree.map((n) => (
- {n.children?.map((c) => (
-
- ))}
+ {n.children && n.children.length ? renderTreeNodes(n.children, workflowMap) : null}
- )) : (
+ ))) : (
No workflows found.
)}
@@ -757,7 +772,7 @@ export default function CreateJob({ onCancel, onCreated, initialJob = null }) {
-
diff --git a/Data/Server/WebUI/src/Scheduling/Scheduled_Jobs_List.jsx b/Data/Server/WebUI/src/Scheduling/Scheduled_Jobs_List.jsx
index b4321a0..bc2b94e 100644
--- a/Data/Server/WebUI/src/Scheduling/Scheduled_Jobs_List.jsx
+++ b/Data/Server/WebUI/src/Scheduling/Scheduled_Jobs_List.jsx
@@ -13,23 +13,28 @@ import {
TableRow,
TableSortLabel,
Switch,
- IconButton,
- Menu,
- MenuItem,
Dialog,
DialogTitle,
DialogContent,
- DialogActions
+ DialogActions,
+ Checkbox,
+ Popover,
+ TextField,
+ IconButton
} from "@mui/material";
-import { MoreHoriz as MoreHorizIcon } from "@mui/icons-material";
+import FilterListIcon from "@mui/icons-material/FilterList";
export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken }) {
const [rows, setRows] = useState([]);
const [orderBy, setOrderBy] = useState("name");
const [order, setOrder] = useState("asc");
- const [menuAnchor, setMenuAnchor] = useState(null);
- const [menuRow, setMenuRow] = useState(null);
- const [deleteOpen, setDeleteOpen] = useState(false);
+ const [selected, setSelected] = useState(new Set());
+ const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false);
+ const [filters, setFilters] = useState({}); // {name, occurrence, lastRun, nextRun}
+ const [filterAnchor, setFilterAnchor] = useState(null); // { id, anchorEl }
+ const openFilter = (id) => (e) => setFilterAnchor({ id, anchorEl: e.currentTarget });
+ const closeFilter = () => setFilterAnchor(null);
+ const onFilterChange = (id) => (e) => setFilters((prev) => ({ ...prev, [id]: e.target.value }));
const loadJobs = async () => {
try {
@@ -85,14 +90,43 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
}
};
+ const filtered = useMemo(() => {
+ const f = filters || {};
+ const match = (val, q) => String(val || "").toLowerCase().includes(String(q || "").toLowerCase());
+ return rows.filter((r) => (
+ (!f.name || match(r.name, f.name)) &&
+ (!f.occurrence || match(r.occurrence, f.occurrence)) &&
+ (!f.lastRun || match(r.lastRun, f.lastRun)) &&
+ (!f.nextRun || match(r.nextRun, f.nextRun))
+ ));
+ }, [rows, filters]);
+
const sorted = useMemo(() => {
const dir = order === "asc" ? 1 : -1;
- return [...rows].sort((a, b) => {
+ return [...filtered].sort((a, b) => {
const A = a[orderBy] || "";
const B = b[orderBy] || "";
return String(A).localeCompare(String(B)) * dir;
});
- }, [rows, orderBy, order]);
+ }, [filtered, orderBy, order]);
+
+ // Selection helpers
+ const anySelected = selected.size > 0;
+ const allSelected = useMemo(() => (sorted.length > 0 && sorted.every(r => selected.has(r.id))), [sorted, selected]);
+ const toggleSelect = (id, checked) => {
+ setSelected((prev) => {
+ const next = new Set(prev);
+ if (checked) next.add(id); else next.delete(id);
+ return next;
+ });
+ };
+ const toggleSelectAll = (checked) => {
+ if (checked) {
+ setSelected(new Set(sorted.map(r => r.id)));
+ } else {
+ setSelected(new Set());
+ }
+ };
const resultColor = (r) => {
if (r === 'Success') return '#00d18c';
@@ -124,14 +158,25 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
{sections.map(({key,color}) => (s[key] ? : null))}
- {['success','running','failed','timed_out','expired','pending']
- .filter(k => s[k])
- .map((k,i) => (
-
- x.key===k).color, marginRight: 6 }} />
- {s[k]} {k.replace('_',' ').replace(/^./, c=>c.toUpperCase())}
-
- ))}
+ {(() => {
+ const nonPendingKeys = ['success','running','failed','timed_out','expired'].filter(k => s[k]);
+ if (nonPendingKeys.length === 0 && s['pending']) {
+ // Pending-only: show simple "Scheduled" label under the bar
+ return Scheduled;
+ }
+ return (
+ <>
+ {['success','running','failed','timed_out','expired','pending']
+ .filter(k => s[k])
+ .map((k) => (
+
+ x.key===k).color, marginRight: 6 }} />
+ {s[k]} {k === 'pending' ? 'Scheduled' : k.replace('_',' ').replace(/^./, c=>c.toUpperCase())}
+
+ ))}
+ >
+ );
+ })()}
);
@@ -156,39 +201,63 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
List of automation jobs with schedules, results, and actions.
- onCreateJob && onCreateJob()}
- >
- Create Job
-
+
+ setBulkDeleteOpen(true)}
+ >
+ Delete Job
+
+ onCreateJob && onCreateJob()}
+ >
+ Create Job
+
+
+
+ toggleSelectAll(e.target.checked)}
+ />
+
{[
["name", "Name"],
["scriptWorkflow", "Script / Workflow"],
["target", "Target"],
- ["occurrence", "Schedule Occurrence"],
+ ["occurrence", "Recurrence"],
["lastRun", "Last Run"],
["nextRun", "Next Run"],
- ["result", "Result"],
- ["results", "Results"],
- ["enabled", "Enabled"],
- ["edit", "Edit Job"]
+ ["results", "Results"],
+ ["enabled", "Enabled"]
].map(([key, label]) => (
- {key !== "edit" && key !== "results" ? (
- handleSort(key)}
- >
- {label}
-
+ {key !== "results" ? (
+
+ handleSort(key)}
+ >
+ {label}
+
+ {['name','occurrence','lastRun','nextRun'].includes(key) ? (
+
+
+
+ ) : null}
+
) : (
label
)}
@@ -199,26 +268,21 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
{sorted.map((r, i) => (
- {r.name}
+
+ toggleSelect(r.id, e.target.checked)} />
+
+
+ { const job = r.raw; if (job && typeof onEditJob === 'function') onEditJob(job); }}
+ sx={{ color: '#58a6ff', textTransform: 'none', p: 0, minWidth: 0 }}
+ >
+ {r.name}
+
+
{r.scriptWorkflow || "Demonstration Component"}
{r.target}
{r.occurrence}
{r.lastRun}
{r.nextRun}
-
-
- {r.result}
-
@@ -238,11 +302,6 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
size="small"
/>
-
- { setMenuAnchor(e.currentTarget); setMenuRow(r); }} sx={{ color: "#58a6ff" }}>
-
-
-
))}
{sorted.length === 0 && (
@@ -254,45 +313,65 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
)}
-
-
-