Updated Scheduled Jobs List

This commit is contained in:
2025-11-09 19:44:23 -07:00
parent e0b0ace444
commit 8a01226454
4 changed files with 237 additions and 207 deletions

View File

@@ -3,7 +3,7 @@
"description": "Deletes Agent and Engine Logs from C:\\Borealis.", "description": "Deletes Agent and Engine Logs from C:\\Borealis.",
"files": [], "files": [],
"name": "Borealis - Delete Logs [WIN]", "name": "Borealis - Delete Logs [WIN]",
"script": "UmVtb3ZlLUl0ZW0gLUxpdGVyYWxQYXRoICdDOlxCb3JlYWxpc1xBZ2VudFxMb2dzJywnQzpcQm9yZWFsaXNcRW5naW5lXExvZ3MnIC1SZWN1cnNlIC1Gb3JjZSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQ==", "script": "UmVtb3ZlLUl0ZW0gLUxpdGVyYWxQYXRoICdDOlxCb3JlYWxpc1xBZ2VudFxMb2dzJywnQzpcQm9yZWFsaXNcRW5naW5lXExvZ3MnIC1SZWN1cnNlIC1Gb3JjZSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQpSZW1vdmUtSXRlbSAtTGl0ZXJhbFBhdGggJ0U6XEdpdEh1YlxCb3JlYWxpc1xBZ2VudFxMb2dzJywnRTpcR2l0SHViXEJvcmVhbGlzXEVuZ2luZVxMb2dzJyAtUmVjdXJzZSAtRm9yY2UgLUVycm9yQWN0aW9uIFNpbGVudGx5Q29udGludWU=",
"script_encoding": "base64", "script_encoding": "base64",
"sites": { "sites": {
"mode": "all", "mode": "all",

View File

@@ -177,13 +177,10 @@ const SAMPLE_ROWS = [
const selectionCol = { const selectionCol = {
headerName: "", headerName: "",
field: "__select__", field: "__select__",
cellClass: 'ag-selection-centered',
cellStyle: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 0 },
width: 52, width: 52,
maxWidth: 52, maxWidth: 52,
checkboxSelection: true, checkboxSelection: true,
headerCheckboxSelection: true, headerCheckboxSelection: true,
headerCheckboxSelectionFilteredOnly: false,
resizable: false, resizable: false,
sortable: false, sortable: false,
suppressMenu: true, suppressMenu: true,

View File

@@ -1,4 +1,12 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/Scheduled_Jobs_List.jsx // /////////////////////////////////////////////////////////////////////////////
// Scheduled_Jobs_List — Borealis MagicUI styling parity with Page Template
// - Aurora gradient shell
// - Small Material icon LEFT of title
// - Subtitle under title
// - Top-right utility buttons (Refresh, Create Job, Settings)
// - AG Grid Quartz theme + square checkboxes, rounded chrome
// - Keeps all original logic + renderers
// /////////////////////////////////////////////////////////////////////////////
import React, { import React, {
useCallback, useCallback,
@@ -16,34 +24,68 @@ import {
Dialog, Dialog,
DialogTitle, DialogTitle,
DialogActions, DialogActions,
CircularProgress CircularProgress,
IconButton,
Stack,
Tooltip
} from "@mui/material"; } from "@mui/material";
import {
Schedule as HeaderIcon,
Cached as CachedIcon,
Add as AddIcon,
Tune as TuneIcon
} from "@mui/icons-material";
import { AgGridReact } from "ag-grid-react"; import { AgGridReact } from "ag-grid-react";
import { ModuleRegistry, AllCommunityModule, themeQuartz } from "ag-grid-community"; import { ModuleRegistry, AllCommunityModule, themeQuartz } from "ag-grid-community";
import { DomainBadge, resolveDomainMeta } from "../Assemblies/Assembly_Badges"; import { DomainBadge, resolveDomainMeta } from "../Assemblies/Assembly_Badges";
import { buildAssemblyIndex, resolveAssemblyForComponent } from "../Assemblies/assemblyUtils"; import { buildAssemblyIndex, resolveAssemblyForComponent } from "../Assemblies/assemblyUtils";
// -----------------------------------------------------------------------------
// Register AG Grid community modules
// -----------------------------------------------------------------------------
ModuleRegistry.registerModules([AllCommunityModule]); ModuleRegistry.registerModules([AllCommunityModule]);
const myTheme = themeQuartz.withParams({ // -----------------------------------------------------------------------------
accentColor: "#FFA6FF", // MagicUI x Quartz Theme (parity with Page_Template)
backgroundColor: "#1f2836", // -----------------------------------------------------------------------------
const gridTheme = themeQuartz.withParams({
accentColor: "#8b5cf6",
backgroundColor: "#070b1a",
browserColorScheme: "dark", browserColorScheme: "dark",
chromeBackgroundColor: { fontFamily: { googleFont: "IBM Plex Sans" },
ref: "foregroundColor", foregroundColor: "#f4f7ff",
mix: 0.07, headerFontSize: 13,
onto: "backgroundColor"
},
fontFamily: {
googleFont: "IBM Plex Sans"
},
foregroundColor: "#FFF",
headerFontSize: 14
}); });
const themeClassName = gridTheme.themeName || "ag-theme-quartz";
const themeClassName = myTheme.themeName || "ag-theme-quartz"; // Typography
const gridFontFamily = '"IBM Plex Sans", "Helvetica Neue", Arial, sans-serif'; const gridFontFamily = "'IBM Plex Sans','Helvetica Neue',Arial,sans-serif";
const iconFontFamily = '"Quartz Regular"'; const iconFontFamily = "'Quartz Regular'";
// Aurora gradient shell colors
const AURORA_SHELL = {
background:
"radial-gradient(120% 120% at 0% 0%, rgba(76, 186, 255, 0.16), transparent 55%), " +
"radial-gradient(120% 120% at 100% 0%, rgba(214, 130, 255, 0.18), transparent 60%), #040711",
text: "#e2e8f0",
subtext: "#9ba3b4",
accent: "#7dd3fc",
};
// Gradient button styling
const gradientButtonSx = {
backgroundImage: "linear-gradient(135deg,#7dd3fc,#c084fc)",
color: "#0b1220",
borderRadius: 999,
textTransform: "none",
boxShadow: "0 10px 26px rgba(124,58,237,0.28)",
px: 2.6,
minWidth: 116,
"&:hover": {
backgroundImage: "linear-gradient(135deg,#86e1ff,#d1a6ff)",
boxShadow: "0 12px 34px rgba(124,58,237,0.38)",
},
};
function ResultsBar({ counts }) { function ResultsBar({ counts }) {
const total = Math.max(1, Number(counts?.total_targets || 0)); const total = Math.max(1, Number(counts?.total_targets || 0));
@@ -67,24 +109,8 @@ function ResultsBar({ counts }) {
.some((section) => Number(counts?.[section.key] || 0) > 0); .some((section) => Number(counts?.[section.key] || 0) > 0);
return ( return (
<Box <Box sx={{ display: "flex", flexDirection: "column", gap: 0.25, lineHeight: 1.7, fontFamily: gridFontFamily }}>
sx={{ <Box sx={{ display: "flex", borderRadius: 1, overflow: "hidden", width: 220, height: 6 }}>
display: "flex",
flexDirection: "column",
gap: 0.25,
lineHeight: 1.7,
fontFamily: gridFontFamily
}}
>
<Box
sx={{
display: "flex",
borderRadius: 1,
overflow: "hidden",
width: 220,
height: 6
}}
>
{sections.map((section) => { {sections.map((section) => {
const value = Number(counts?.[section.key] || 0); const value = Number(counts?.[section.key] || 0);
if (!value) return null; if (!value) return null;
@@ -116,20 +142,8 @@ function ResultsBar({ counts }) {
return sections return sections
.filter((section) => Number(counts?.[section.key] || 0) > 0) .filter((section) => Number(counts?.[section.key] || 0) > 0)
.map((section) => ( .map((section) => (
<Box <Box key={section.key} component="span" sx={{ display: "inline-flex", alignItems: "center", gap: 0.5 }}>
key={section.key} <Box component="span" sx={{ width: 6, height: 6, borderRadius: 1, backgroundColor: section.color }} />
component="span"
sx={{ display: "inline-flex", alignItems: "center", gap: 0.5 }}
>
<Box
component="span"
sx={{
width: 6,
height: 6,
borderRadius: 1,
backgroundColor: section.color
}}
/>
{counts?.[section.key]} {labelFor(section.key)} {counts?.[section.key]} {labelFor(section.key)}
</Box> </Box>
)); ));
@@ -478,12 +492,8 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
onChange={handleToggle} onChange={handleToggle}
onClick={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()}
sx={{ sx={{
"& .MuiSwitch-switchBase.Mui-checked": { "& .MuiSwitch-switchBase.Mui-checked": { color: "#58a6ff" },
color: "#58a6ff" "& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track": { bgcolor: "#58a6ff" }
},
"& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track": {
bgcolor: "#58a6ff"
}
}} }}
/> />
); );
@@ -491,49 +501,42 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
[] []
); );
// Selection column parity (square, centered, pinned left)
const selectionCol = {
headerName: "",
field: "__select__",
width: 52,
maxWidth: 52,
checkboxSelection: true,
headerCheckboxSelection: true,
resizable: false,
sortable: false,
suppressMenu: true,
filter: false,
pinned: "left",
lockPosition: true,
};
const columnDefs = useMemo( const columnDefs = useMemo(
() => [ () => [
{ selectionCol,
headerName: "",
field: "__checkbox__",
checkboxSelection: true,
headerCheckboxSelection: true,
maxWidth: 60,
minWidth: 60,
sortable: false,
filter: false,
resizable: false,
suppressMenu: true,
pinned: false
},
{ {
headerName: "Name", headerName: "Name",
field: "name", field: "name",
cellRenderer: nameCellRenderer, cellRenderer: nameCellRenderer,
sort: "asc" sort: "asc",
minWidth: 220,
}, },
{ {
headerName: "Assembly(s)", headerName: "Assembly(s)",
field: "componentsMeta", field: "componentsMeta",
minWidth: 240, minWidth: 260,
cellRenderer: assembliesCellRenderer cellRenderer: assembliesCellRenderer
}, },
{ { headerName: "Target", field: "target", minWidth: 140 },
headerName: "Target", { headerName: "Recurrence", field: "occurrence", minWidth: 160 },
field: "target" { headerName: "Last Run", field: "lastRun", minWidth: 160 },
}, { headerName: "Next Run", field: "nextRun", minWidth: 160 },
{
headerName: "Recurrence",
field: "occurrence"
},
{
headerName: "Last Run",
field: "lastRun"
},
{
headerName: "Next Run",
field: "nextRun"
},
{ {
headerName: "Results", headerName: "Results",
field: "resultsCounts", field: "resultsCounts",
@@ -562,7 +565,6 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
sortable: true, sortable: true,
filter: "agTextColumnFilter", filter: "agTextColumnFilter",
resizable: true, resizable: true,
flex: 1,
minWidth: 140, minWidth: 140,
cellStyle: { cellStyle: {
display: "flex", display: "flex",
@@ -570,145 +572,181 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
color: "#f5f7fa", color: "#f5f7fa",
fontFamily: gridFontFamily, fontFamily: gridFontFamily,
fontSize: "13px" fontSize: "13px"
}, }
headerClass: "scheduled-jobs-grid-header"
}), }),
[] []
); );
const handleRefreshClick = useCallback(async () => {
await loadJobs({ showLoading: true });
}, [loadJobs]);
return ( return (
<Paper <Paper
sx={{ sx={{
m: 2, m: 0,
p: 0, p: 0,
bgcolor: "#1e1e1e", background: AURORA_SHELL.background,
color: "#f5f7fa", border: "none",
fontFamily: gridFontFamily, boxShadow: "none",
borderRadius: 0,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
flexGrow: 1, flexGrow: 1,
minWidth: 0, minWidth: 0,
minHeight: 420 minHeight: 420,
height: "100%",
color: AURORA_SHELL.text,
fontFamily: gridFontFamily,
}} }}
elevation={2} elevation={0}
> >
<Box {/* Page header (keep padding: top 24px, left/right 24px) */}
sx={{ <Box sx={{ px: 3, pt: 3, pb: 1 }}>
display: "flex", <Stack direction="row" alignItems="center" spacing={1.25}>
alignItems: "center", <HeaderIcon sx={{ fontSize: 22, color: AURORA_SHELL.accent }} />
justifyContent: "space-between", <Typography variant="h6" sx={{ fontWeight: 700, letterSpacing: 0.5 }}>
p: 2,
borderBottom: "1px solid #2a2a2a"
}}
>
<Box>
<Typography variant="h6" sx={{ color: "#58a6ff", mb: 0.3 }}>
Scheduled Jobs Scheduled Jobs
</Typography> </Typography>
<Typography variant="body2" sx={{ color: "#aaa" }}> <Box sx={{ flexGrow: 1 }} />
List of automation jobs with schedules, results, and actions. <Stack direction="row" spacing={1}>
</Typography> <Tooltip title="Refresh">
</Box> <span>
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}> <IconButton
<Button size="small"
variant="outlined" onClick={handleRefreshClick}
size="small" sx={{ color: "#cbd5e1", borderRadius: 1, "&:hover": { color: "#ffffff" } }}
disabled={!anySelected} >
sx={{ <CachedIcon fontSize="small" />
color: anySelected ? "#ff8080" : "#666", </IconButton>
borderColor: anySelected ? "#ff8080" : "#333", </span>
textTransform: "none", </Tooltip>
fontFamily: gridFontFamily, <Tooltip title="Create Job">
"&:hover": { <span>
borderColor: anySelected ? "#ff8080" : "#333" <Button size="small" startIcon={<AddIcon />} sx={gradientButtonSx} onClick={() => onCreateJob && onCreateJob()}>
} Create Job
}} </Button>
onClick={() => setBulkDeleteOpen(true)} </span>
> </Tooltip>
Delete Job <Tooltip title="Settings">
</Button> <span>
<Button <Button
variant="contained" size="small"
size="small" variant="outlined"
sx={{ startIcon={<TuneIcon />}
bgcolor: "#58a6ff", sx={{
color: "#0b0f19", borderColor: "rgba(148,163,184,0.35)",
textTransform: "none", color: "#e2e8f0",
fontFamily: gridFontFamily, textTransform: "none",
"&:hover": { borderRadius: 999,
bgcolor: "#7db7ff" px: 1.7,
} minWidth: 86,
}} "&:hover": { borderColor: "rgba(148,163,184,0.55)" },
onClick={() => onCreateJob && onCreateJob()} }}
> >
Create Job Settings
</Button> </Button>
</Box> </span>
</Tooltip>
</Stack>
</Stack>
{/* Subtitle directly under title (muted color, small size) */}
<Typography variant="body2" sx={{ color: AURORA_SHELL.subtext, mt: 0.75 }}>
List of automation jobs with schedules, results, and actions.
</Typography>
</Box> </Box>
{loading && ( {/* Content area — a bit more top space below subtitle */}
<Box <Box sx={{ mt: "28px", px: 2, pb: 2, flexGrow: 1, minHeight: 0, display: "flex", flexDirection: "column" }}>
sx={{
display: "flex",
alignItems: "center",
gap: 1,
color: "#7db7ff",
px: 2,
py: 1.5,
borderBottom: "1px solid #2a2a2a"
}}
>
<CircularProgress size={18} sx={{ color: "#58a6ff" }} />
<Typography variant="body2">Loading scheduled jobs</Typography>
</Box>
)}
{error && (
<Box sx={{ px: 2, py: 1.5, color: "#ff8080", borderBottom: "1px solid #2a2a2a" }}>
<Typography variant="body2">{error}</Typography>
</Box>
)}
<Box
sx={{
flexGrow: 1,
minHeight: 0,
display: "flex",
flexDirection: "column",
mt: "10px",
px: 2,
pb: 2
}}
>
<Box <Box
className={themeClassName} className={themeClassName}
sx={{ sx={{
background: "transparent",
border: "none",
p: 0,
width: "100%", width: "100%",
height: "100%",
flexGrow: 1, flexGrow: 1,
minHeight: 0,
height: "100%",
position: "relative",
fontFamily: gridFontFamily, fontFamily: gridFontFamily,
"--ag-font-family": gridFontFamily, "--ag-font-family": gridFontFamily,
"--ag-icon-font-family": iconFontFamily, "--ag-icon-font-family": iconFontFamily,
"--ag-row-border-style": "solid",
"--ag-row-border-color": "#2a2a2a", "& .ag-cell": {
"--ag-row-border-width": "1px", display: "flex",
"& .ag-root-wrapper": { alignItems: "center",
borderRadius: 1, paddingTop: "8px",
minHeight: 320 paddingBottom: "8px",
}, },
"& .ag-root, & .ag-header, & .ag-center-cols-container, & .ag-paging-panel": {
fontFamily: gridFontFamily /* Center the selection column (header + body) */
"& .ag-header .ag-header-select-all, & .ag-header .ag-checkbox-input-wrapper": {
display: "flex",
alignItems: "center",
justifyContent: "center",
}, },
"& .ag-icon": { "& .ag-cell.ag-selection-centered": {
fontFamily: iconFontFamily display: "flex",
alignItems: "center",
justifyContent: "center",
paddingLeft: 0,
paddingRight: 0,
}, },
"& .scheduled-jobs-grid-header": { "& .ag-cell.ag-selection-centered .ag-cell-wrapper": {
fontFamily: gridFontFamily, gap: "0 !important",
fontWeight: 600, width: "100%",
color: "#f5f7fa" display: "flex",
} alignItems: "center",
justifyContent: "center",
paddingTop: 0,
paddingBottom: 0,
},
"& .ag-cell.ag-selection-centered .ag-selection-checkbox, & .ag-cell.ag-selection-centered .ag-checkbox-input-wrapper": {
margin: "0 auto",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
}}
style={{
"--ag-background-color": "#070b1a",
"--ag-foreground-color": "#f4f7ff",
"--ag-header-background-color": "#0f172a",
"--ag-header-foreground-color": "#cfe0ff",
"--ag-odd-row-background-color": "rgba(255,255,255,0.02)",
"--ag-row-hover-color": "rgba(125,183,255,0.08)",
"--ag-selected-row-background-color": "rgba(64,164,255,0.18)",
"--ag-border-color": "rgba(125,183,255,0.18)",
"--ag-row-border-color": "rgba(125,183,255,0.14)",
"--ag-border-radius": "8px",
"--ag-checkbox-border-radius": "3px",
"--ag-checkbox-background-color": "rgba(255,255,255,0.06)",
"--ag-checkbox-border-color": "rgba(180,200,220,0.6)",
"--ag-checkbox-checked-color": "#7dd3fc",
}} }}
> >
{/* Action bar for bulk delete stays above grid when needed */}
{anySelected && (
<Box sx={{ display: "flex", alignItems: "center", gap: 1, color: "#7db7ff", px: 1.5, py: 1 }}>
<Button
variant="outlined"
size="small"
sx={{
color: "#ff8080",
borderColor: "rgba(255,128,128,0.5)",
textTransform: "none",
borderRadius: 999,
"&:hover": { borderColor: "#ff8080" },
}}
onClick={() => setBulkDeleteOpen(true)}
>
Delete Job
</Button>
</Box>
)}
<AgGridReact <AgGridReact
rowData={rows} rowData={rows}
columnDefs={columnDefs} columnDefs={columnDefs}
@@ -724,13 +762,8 @@ export default function ScheduledJobsList({ onCreateJob, onEditJob, refreshToken
overlayNoRowsTemplate="<span class='ag-overlay-no-rows-center'>No scheduled jobs found.</span>" overlayNoRowsTemplate="<span class='ag-overlay-no-rows-center'>No scheduled jobs found.</span>"
onGridReady={handleGridReady} onGridReady={handleGridReady}
onSelectionChanged={handleSelectionChanged} onSelectionChanged={handleSelectionChanged}
theme={myTheme} theme={gridTheme}
style={{ style={{ width: "100%", height: "100%", fontFamily: gridFontFamily, "--ag-icon-font-family": iconFontFamily }}
width: "100%",
height: "100%",
fontFamily: gridFontFamily,
"--ag-icon-font-family": iconFontFamily
}}
/> />
</Box> </Box>
</Box> </Box>