diff --git a/Data/Engine/web-interface/src/Access_Management/Credential_Editor.jsx b/Data/Engine/web-interface/src/Access_Management/Credential_Editor.jsx
index 88b293d0..e20dd45d 100644
--- a/Data/Engine/web-interface/src/Access_Management/Credential_Editor.jsx
+++ b/Data/Engine/web-interface/src/Access_Management/Credential_Editor.jsx
@@ -18,6 +18,55 @@ import {
} from "@mui/material";
import UploadIcon from "@mui/icons-material/UploadFile";
import ClearIcon from "@mui/icons-material/Clear";
+import VpnKeyIcon from "@mui/icons-material/VpnKey";
+
+const MAGIC_UI = {
+ panelBg: "rgba(8,12,24,0.96)",
+ panelBorder: "rgba(148,163,184,0.35)",
+ textBright: "#e2e8f0",
+ textMuted: "#94a3b8",
+ accentA: "#7dd3fc",
+ accentB: "#c084fc",
+ danger: "#ff8a8a",
+};
+
+const GRADIENT_BUTTON_SX = {
+ backgroundImage: "linear-gradient(135deg,#7dd3fc,#c084fc)",
+ color: "#041224",
+ borderRadius: 999,
+ textTransform: "none",
+ fontWeight: 700,
+ boxShadow: "0 12px 30px rgba(124,58,237,0.32)",
+ "&:hover": {
+ backgroundImage: "linear-gradient(135deg,#86e1ff,#d1a6ff)",
+ boxShadow: "0 14px 38px rgba(124,58,237,0.42)",
+ },
+};
+
+const OUTLINE_BUTTON_SX = {
+ textTransform: "none",
+ borderRadius: 2,
+ borderColor: MAGIC_UI.panelBorder,
+ color: MAGIC_UI.textBright,
+ "&:hover": { borderColor: MAGIC_UI.accentA },
+};
+
+const INPUT_SX = {
+ "& .MuiOutlinedInput-root": {
+ backgroundColor: "rgba(12,18,35,0.75)",
+ color: MAGIC_UI.textBright,
+ borderRadius: 2,
+ "& fieldset": { borderColor: MAGIC_UI.panelBorder },
+ "&:hover fieldset": { borderColor: MAGIC_UI.accentA },
+ "&.Mui-focused fieldset": { borderColor: MAGIC_UI.accentA },
+ },
+ "& .MuiOutlinedInput-input": {
+ padding: "10px 12px",
+ },
+ "& .MuiInputLabel-root": {
+ color: MAGIC_UI.textMuted,
+ },
+};
const CREDENTIAL_TYPES = [
{ value: "machine", label: "Machine" },
@@ -280,7 +329,7 @@ export default function CredentialEditor({
};
const title = isEdit ? "Edit Credential" : "Create Credential";
- const helperStyle = { fontSize: 12, color: "#8a8a8a", mt: 0.5 };
+ const helperStyle = { fontSize: 12, color: MAGIC_UI.textMuted, mt: 0.5 };
return (
diff --git a/Data/Engine/web-interface/src/Access_Management/Credential_List.jsx b/Data/Engine/web-interface/src/Access_Management/Credential_List.jsx
index 7c91422d..0e0e5184 100644
--- a/Data/Engine/web-interface/src/Access_Management/Credential_List.jsx
+++ b/Data/Engine/web-interface/src/Access_Management/Credential_List.jsx
@@ -41,6 +41,18 @@ const myTheme = themeQuartz.withParams({
const themeClassName = myTheme.themeName || "ag-theme-quartz";
const gridFontFamily = '"IBM Plex Sans", "Helvetica Neue", Arial, sans-serif';
const iconFontFamily = '"Quartz Regular"';
+const gradientButtonSx = {
+ backgroundImage: "linear-gradient(135deg,#7dd3fc,#c084fc)",
+ color: "#0b1220",
+ borderRadius: 999,
+ textTransform: "none",
+ boxShadow: "0 10px 26px rgba(124,58,237,0.28)",
+ "&:hover": {
+ backgroundImage: "linear-gradient(135deg,#86e1ff,#d1a6ff)",
+ boxShadow: "0 12px 34px rgba(124,58,237,0.38)",
+ filter: "none",
+ },
+};
function formatTs(ts) {
if (!ts) return "-";
@@ -62,7 +74,7 @@ function connectionIcon(connection) {
return ;
}
-export default function CredentialList({ isAdmin = false }) {
+export default function CredentialList({ isAdmin = false, onPageMetaChange }) {
const [rows, setRows] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
@@ -216,6 +228,15 @@ export default function CredentialList({ isAdmin = false }) {
fetchCredentials();
}, [fetchCredentials]);
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: "Credentials",
+ page_subtitle: "Stored credentials for remote automation tasks and Ansible playbook runs.",
+ page_icon: LockIcon,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange]);
+
const handleCreate = () => {
setEditorMode("create");
setEditingCredential(null);
@@ -291,9 +312,11 @@ export default function CredentialList({ isAdmin = false }) {
<>
-
-
- Credentials
-
-
- Stored credentials for remote automation tasks and Ansible playbook runs.
-
-
}
- sx={{ borderColor: "#58a6ff", color: "#58a6ff" }}
+ sx={{
+ borderColor: "rgba(148,163,184,0.35)",
+ color: "#e2e8f0",
+ textTransform: "none",
+ borderRadius: 999,
+ px: 1.7,
+ minWidth: 86,
+ "&:hover": { borderColor: "rgba(148,163,184,0.55)" },
+ }}
onClick={fetchCredentials}
disabled={loading}
>
@@ -336,7 +360,7 @@ export default function CredentialList({ isAdmin = false }) {
variant="contained"
size="small"
startIcon={}
- sx={{ bgcolor: "#58a6ff", color: "#0b0f19" }}
+ sx={gradientButtonSx}
onClick={handleCreate}
>
New Credential
@@ -352,7 +376,6 @@ export default function CredentialList({ isAdmin = false }) {
color: "#7db7ff",
px: 2,
py: 1.5,
- borderBottom: "1px solid #2a2a2a"
}}
>
@@ -360,7 +383,7 @@ export default function CredentialList({ isAdmin = false }) {
)}
{error && (
-
+
{error}
)}
diff --git a/Data/Engine/web-interface/src/Access_Management/Github_API_Token.jsx b/Data/Engine/web-interface/src/Access_Management/Github_API_Token.jsx
index af6f9177..e0b60a33 100644
--- a/Data/Engine/web-interface/src/Access_Management/Github_API_Token.jsx
+++ b/Data/Engine/web-interface/src/Access_Management/Github_API_Token.jsx
@@ -13,11 +13,14 @@ import RefreshIcon from "@mui/icons-material/Refresh";
import SaveIcon from "@mui/icons-material/Save";
import VisibilityIcon from "@mui/icons-material/Visibility";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
+import GitHubIcon from "@mui/icons-material/GitHub";
const paperSx = {
- m: 2,
+ m: 0,
p: 0,
bgcolor: "transparent",
+ border: "none",
+ boxShadow: "none",
color: "#f5f7fa",
display: "flex",
flexDirection: "column",
@@ -29,17 +32,31 @@ const paperSx = {
const fieldSx = {
mt: 2,
"& .MuiOutlinedInput-root": {
- bgcolor: "#181818",
+ bgcolor: "rgba(255,255,255,0.04)",
color: "#f5f7fa",
- "& fieldset": { borderColor: "#2a2a2a" },
- "&:hover fieldset": { borderColor: "#58a6ff" },
- "&.Mui-focused fieldset": { borderColor: "#58a6ff" }
+ borderRadius: 1,
+ "& fieldset": { borderColor: "rgba(148,163,184,0.35)" },
+ "&:hover fieldset": { borderColor: "rgba(148,163,184,0.55)" },
+ "&.Mui-focused fieldset": { borderColor: "#7dd3fc" }
},
"& .MuiInputLabel-root": { color: "#bbb" },
"& .MuiInputLabel-root.Mui-focused": { color: "#7db7ff" }
};
-export default function GithubAPIToken({ isAdmin = false }) {
+const gradientButtonSx = {
+ backgroundImage: "linear-gradient(135deg,#7dd3fc,#c084fc)",
+ color: "#0b1220",
+ borderRadius: 999,
+ textTransform: "none",
+ boxShadow: "0 10px 26px rgba(124,58,237,0.28)",
+ "&:hover": {
+ backgroundImage: "linear-gradient(135deg,#86e1ff,#d1a6ff)",
+ boxShadow: "0 12px 34px rgba(124,58,237,0.38)",
+ filter: "none",
+ },
+};
+
+export default function GithubAPIToken({ isAdmin = false, onPageMetaChange }) {
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [token, setToken] = useState("");
@@ -145,6 +162,15 @@ export default function GithubAPIToken({ isAdmin = false }) {
setShowToken((prev) => !prev);
}, []);
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: "GitHub API Token",
+ page_subtitle: "Increase GitHub API rate limits for Borealis by storing a personal access token.",
+ page_icon: GitHubIcon,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange]);
+
if (!isAdmin) {
return (
@@ -159,23 +185,10 @@ export default function GithubAPIToken({ isAdmin = false }) {
}
return (
-
-
-
-
- Github API Token
-
-
- Using a Github "Personal Access Token" increases the Github API rate limits from 60/hr to 5,000/hr. This is important for production Borealis usage as it likes to hit its unauthenticated API limits sometimes despite my best efforts.
-
Navigate to{' '}
+
+
+
+ Using a GitHub Personal Access Token raises rate limits from 60/hr to 5,000/hr. Generate one at{" "}
https://github.com/settings/tokens
- {' '}
- ❯ Personal Access Tokens ❯ Tokens (Classic) ❯ Generate New Token ❯ New Personal Access Token (Classic)
-
-
-
+ {" "}
+ under Personal Access Tokens → Tokens (Classic).
+
- Note:{' '}
+ Note:{" "}
Borealis Automation Platform
-
-
- Scope:{' '}
+ {" "}
+ Scope:{" "}
public_repo
-
-
- Expiration:{' '}
+ {" "}
+ Expiration:{" "}
No Expiration
-
-
-
+
: null}
- sx={{
- bgcolor: "#58a6ff",
- color: "#0b0f19",
- minWidth: 88,
- mr: 1,
- "&:hover": { bgcolor: "#7db7ff" }
- }}
+ sx={gradientButtonSx}
>
{saving ? : "Save"}
@@ -273,7 +274,15 @@ export default function GithubAPIToken({ isAdmin = false }) {
variant="outlined"
size="small"
startIcon={}
- sx={{ borderColor: "#58a6ff", color: "#58a6ff" }}
+ sx={{
+ borderColor: "rgba(148,163,184,0.35)",
+ color: "#e2e8f0",
+ textTransform: "none",
+ borderRadius: 999,
+ px: 1.7,
+ minWidth: 86,
+ "&:hover": { borderColor: "rgba(148,163,184,0.55)" },
+ }}
onClick={hydrate}
disabled={loading || saving}
>
diff --git a/Data/Engine/web-interface/src/Access_Management/Users.jsx b/Data/Engine/web-interface/src/Access_Management/Users.jsx
index 547c3c91..6330397f 100644
--- a/Data/Engine/web-interface/src/Access_Management/Users.jsx
+++ b/Data/Engine/web-interface/src/Access_Management/Users.jsx
@@ -27,10 +27,11 @@ import {
} from "@mui/material";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import FilterListIcon from "@mui/icons-material/FilterList";
+import GroupIcon from "@mui/icons-material/Group";
import { ConfirmDeleteDialog } from "../Dialogs.jsx";
/* ---------- Formatting helpers to keep this page in lockstep with Device_List ---------- */
-const tablePaperSx = { m: 2, p: 0, bgcolor: "transparent" };
+const tablePaperSx = { m: 0, p: 0, bgcolor: "transparent", border: "none", boxShadow: "none" };
const tableSx = {
minWidth: 820,
"& th, & td": {
@@ -51,6 +52,19 @@ const filterFieldSx = {
"&:hover fieldset": { borderColor: "#888" }
}
};
+
+const gradientButtonSx = {
+ backgroundImage: "linear-gradient(135deg,#7dd3fc,#c084fc)",
+ color: "#0b1220",
+ borderRadius: 999,
+ textTransform: "none",
+ boxShadow: "0 10px 26px rgba(124,58,237,0.28)",
+ "&:hover": {
+ backgroundImage: "linear-gradient(135deg,#86e1ff,#d1a6ff)",
+ boxShadow: "0 12px 34px rgba(124,58,237,0.38)",
+ filter: "none",
+ },
+};
/* -------------------------------------------------------------------- */
function formatTs(tsSec) {
@@ -69,7 +83,7 @@ async function sha512(text) {
return arr.map((b) => b.toString(16).padStart(2, "0")).join("");
}
-export default function UserManagement({ isAdmin = false }) {
+export default function UserManagement({ isAdmin = false, onPageMetaChange }) {
const [rows, setRows] = useState([]); // {username, display_name, role, last_login}
const [orderBy, setOrderBy] = useState("username");
const [order, setOrder] = useState("asc");
@@ -91,6 +105,7 @@ export default function UserManagement({ isAdmin = false }) {
const [mfaBusyUser, setMfaBusyUser] = useState(null);
const [resetMfaOpen, setResetMfaOpen] = useState(false);
const [resetMfaTarget, setResetMfaTarget] = useState(null);
+ const useGlobalHeader = Boolean(onPageMetaChange);
// Columns and filters
const columns = useMemo(() => ([
@@ -140,6 +155,15 @@ export default function UserManagement({ isAdmin = false }) {
fetchUsers();
}, [fetchUsers, isAdmin]);
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: "User Management",
+ page_subtitle: "Manage platform users, roles, MFA, and credentials.",
+ page_icon: GroupIcon,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange]);
+
const handleSort = (col) => {
if (orderBy === col) setOrder(order === "asc" ? "desc" : "asc");
else { setOrderBy(col); setOrder("asc"); }
@@ -397,21 +421,13 @@ export default function UserManagement({ isAdmin = false }) {
return (
<>
-
-
-
-
- User Management
-
-
- Manage authorized users of the Borealis Automation Platform.
-
-
+
+
Create User
diff --git a/Data/Engine/web-interface/src/Admin/Log_Management.jsx b/Data/Engine/web-interface/src/Admin/Log_Management.jsx
index 04e21d3d..c382c3e4 100644
--- a/Data/Engine/web-interface/src/Admin/Log_Management.jsx
+++ b/Data/Engine/web-interface/src/Admin/Log_Management.jsx
@@ -109,7 +109,7 @@ function formatTimestamp(ts) {
return ts;
}
-export default function LogManagement({ isAdmin = false }) {
+export default function LogManagement({ isAdmin = false, onPageMetaChange }) {
const [logs, setLogs] = useState([]);
const [defaultRetention, setDefaultRetention] = useState(30);
const [selectedDomain, setSelectedDomain] = useState(null);
@@ -124,6 +124,7 @@ export default function LogManagement({ isAdmin = false }) {
const [actionMessage, setActionMessage] = useState(null);
const [quickFilter, setQuickFilter] = useState("");
const gridRef = useRef(null);
+ const useGlobalHeader = Boolean(onPageMetaChange);
const logMap = useMemo(() => {
const map = new Map();
@@ -214,6 +215,15 @@ const defaultColDef = useMemo(
if (isAdmin) fetchLogs();
}, [fetchLogs, isAdmin]);
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: "Log Management",
+ page_subtitle: "Analyze engine logs and adjust retention across services.",
+ page_icon: LogsIcon,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange]);
+
useEffect(() => {
if (!logs.length) {
setSelectedDomain(null);
@@ -317,7 +327,7 @@ const defaultColDef = useMemo(
return (
-
-
-
-
- Log Management
-
-
-
- }
- onClick={() => {
- fetchLogs();
- if (selectedFile) fetchEntries(selectedFile);
+ {!useGlobalHeader && (
+
+
+
+
- Refresh
-
+ Log Management
+
+
+
+ }
+ onClick={() => {
+ fetchLogs();
+ if (selectedFile) fetchEntries(selectedFile);
+ }}
+ sx={gradientButtonSx}
+ >
+ Refresh
+
+
-
-
- Analyze engine logs and adjust log retention periods for different engine services.
-
-
+
+ Analyze engine logs and adjust log retention periods for different engine services.
+
+
+ )}
+ {useGlobalHeader && (
+
+ }
+ onClick={() => {
+ fetchLogs();
+ if (selectedFile) fetchEntries(selectedFile);
+ }}
+ sx={gradientButtonSx}
+ >
+ Refresh
+
+
+ )}
{error && (
diff --git a/Data/Engine/web-interface/src/Admin/Page_Template.jsx b/Data/Engine/web-interface/src/Admin/Page_Template.jsx
index 0afb1a1a..51edace3 100644
--- a/Data/Engine/web-interface/src/Admin/Page_Template.jsx
+++ b/Data/Engine/web-interface/src/Admin/Page_Template.jsx
@@ -1,4 +1,4 @@
-import React, { useMemo, useRef, useCallback } from "react";
+import React, { useMemo, useRef, useCallback, useEffect } from "react";
import {
Paper,
Box,
@@ -215,7 +215,7 @@ const iconFontFamily = "'Quartz Regular'";
// -----------------------------------------------------------------------------
// Page Template Component
// -----------------------------------------------------------------------------
-export default function PageTemplate() {
+export default function PageTemplate({ onPageMetaChange }) {
const gridRef = useRef(null);
const columnDefs = useMemo(() => sampleColumnDefs, []);
const getRowId = useCallback((params) => params.data?.id || String(params.rowIndex ?? ""), []);
@@ -224,6 +224,15 @@ export default function PageTemplate() {
console.log("Refresh clicked (template; no-op).");
};
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: "Page Template",
+ page_subtitle: "Page Styling Guide and Template — use as a baseline when designing new pages.",
+ page_icon: TemplateIcon,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange]);
+
return (
- {/* Page header (keep padding: top 24px, left/right 24px) */}
-
-
-
-
- Page Template
-
-
-
-
-
-
-
-
-
-
-
-
- } sx={gradientButtonSx}>
- New Item
-
-
-
-
-
- }
- sx={{
- borderColor: "rgba(148,163,184,0.35)",
- color: "#e2e8f0",
- textTransform: "none",
- borderRadius: 999,
- px: 1.7, // ~5% wider than previous
- minWidth: 86,
- "&:hover": { borderColor: "rgba(148,163,184,0.55)" },
- }}
- >
- Settings
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ } sx={gradientButtonSx}>
+ New Item
+
+
+
+
+
+ }
+ sx={{
+ borderColor: "rgba(148,163,184,0.35)",
+ color: "#e2e8f0",
+ textTransform: "none",
+ borderRadius: 999,
+ px: 1.7, // ~5% wider than previous
+ minWidth: 86,
+ "&:hover": { borderColor: "rgba(148,163,184,0.55)" },
+ }}
+ >
+ Settings
+
+
+
-
- {/* Subtitle directly under title (muted color, small size) */}
-
- Page Styling Guide and Template — use as a baseline when designing new pages.
-
- {/* Content area — add a little more top-space below subtitle */}
-
+ {/* Content area — offset a little below the shared header */}
+
{ isMounted = false; clearInterval(id); };
}, [isAdmin]);
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: "Server Info",
+ page_subtitle: "Basic server information and project links for debugging and support.",
+ page_icon: InfoIcon,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange]);
+
if (!isAdmin) return null;
return (
-
+
- Server Info
- Basic server information will appear here for informative and debug purposes.
+
+ Basic server information for debug and support. Server time updates automatically every minute.
+
Server Time
@@ -44,23 +67,29 @@ export default function ServerInfo({ isAdmin = false }) {
- Project Links
+ Project Links
}
onClick={() => window.open("https://github.com/bunny-lab-io/Borealis", "_blank")}
- sx={{ borderColor: '#3a3a3a', color: '#7db7ff' }}
+ sx={{
+ borderColor: "rgba(148,163,184,0.35)",
+ color: "#e2e8f0",
+ textTransform: "none",
+ borderRadius: 999,
+ px: 1.7,
+ minWidth: 86,
+ "&:hover": { borderColor: "rgba(148,163,184,0.55)" },
+ }}
>
GitHub Project
}
onClick={() => setAboutOpen(true)}
- sx={{ borderColor: '#3a3a3a', color: '#ddd' }}
+ sx={gradientButtonSx}
>
About Borealis
diff --git a/Data/Engine/web-interface/src/App.jsx b/Data/Engine/web-interface/src/App.jsx
index 33715545..bbd8c701 100644
--- a/Data/Engine/web-interface/src/App.jsx
+++ b/Data/Engine/web-interface/src/App.jsx
@@ -130,6 +130,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
const pendingPathRef = useRef(null);
const quickJobSeedRef = useRef(0);
const [notAuthorizedOpen, setNotAuthorizedOpen] = useState(false);
+ const [pageHeader, setPageHeader] = useState({ title: "", subtitle: "", Icon: null });
// Top-bar search state
const SEARCH_CATEGORIES = [
@@ -171,6 +172,21 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
}
}, []);
+ const handlePageMetaChange = useCallback((meta) => {
+ if (!meta) {
+ setPageHeader({ title: "", subtitle: "", Icon: null });
+ return;
+ }
+ const titleValue = typeof meta.page_title === "string" ? meta.page_title : meta.title;
+ const subtitleValue = typeof meta.page_subtitle === "string" ? meta.page_subtitle : meta.subtitle;
+ const iconValue = meta.page_icon || meta.Icon || null;
+ setPageHeader({
+ title: typeof titleValue === "string" ? titleValue : "",
+ subtitle: typeof subtitleValue === "string" ? subtitleValue : "",
+ Icon: iconValue || null,
+ });
+ }, []);
+
const pageToPath = useCallback(
(page, options = {}) => {
switch (page) {
@@ -1076,6 +1092,10 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
[navigateTo, setTabs, setActiveTabId]
);
+ useEffect(() => {
+ setPageHeader({ title: "", subtitle: "", Icon: null });
+ }, [currentPage]);
+
const isAdmin = (String(userRole || '').toLowerCase() === 'admin');
useEffect(() => {
@@ -1100,6 +1120,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
case "sites":
return (
{
try {
localStorage.setItem('device_list_initial_site_filter', String(siteName || ''));
@@ -1111,6 +1132,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
case "devices":
return (
{
navigateTo("device_details", { device: d });
}}
@@ -1120,6 +1142,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
case "agent_devices":
return (
{
navigateTo("device_details", { device: d });
}}
@@ -1127,13 +1150,14 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
/>
);
case "ssh_devices":
- return ;
+ return ;
case "winrm_devices":
- return ;
+ return ;
case "filters":
return (
{
setFilterEditorState(null);
@@ -1149,6 +1173,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
case "filter_editor":
return (
{
setFilterEditorState(null);
@@ -1165,6 +1190,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
case "device_details":
return (
{
@@ -1177,6 +1203,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
case "jobs":
return (
{ setEditingJob(null); navigateTo("create_job"); }}
onEditJob={(job) => { setEditingJob(job); navigateTo("create_job"); }}
refreshToken={jobsRefreshToken}
@@ -1186,6 +1213,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
case "create_job":
return (
{
setAssemblyEditorState((prev) => (prev && prev.mode === 'script' ? null : prev));
@@ -1238,6 +1269,7 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
return (
{
setAssemblyEditorState((prev) => (prev && prev.mode === 'ansible' ? null : prev));
@@ -1248,24 +1280,24 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
);
case "access_credentials":
- return ;
+ return ;
case "access_github_token":
- return ;
+ return ;
case "access_users":
- return ;
+ return ;
case "server_info":
- return ;
+ return ;
case "log_management":
- return ;
+ return ;
case "page_template":
- return ;
+ return ;
case "admin_device_approvals":
- return ;
+ return ;
case "workflow-editor":
return (
@@ -1335,6 +1367,9 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
);
}
+ const HeaderIcon = pageHeader.Icon;
+ const hasPageHeader = Boolean(pageHeader.title);
+
return (
@@ -1484,18 +1519,45 @@ const LOCAL_STORAGE_KEY = "borealis_persistent_state";
*': {
- alignSelf: 'stretch',
- minHeight: 'calc(100% - 32px)' // account for typical m:2 top+bottom margins
- }
}}
>
- {renderMainContent()}
+ {hasPageHeader ? (
+
+
+ {HeaderIcon ? : null}
+
+ {pageHeader.title}
+
+
+ {pageHeader.subtitle ? (
+
+ {pageHeader.subtitle}
+
+ ) : (
+
+ )}
+
+ ) : null}
+ *": {
+ alignSelf: "stretch",
+ minHeight: "calc(100% - 32px)", // account for typical m:2 top+bottom margins
+ },
+ }}
+ >
+ {renderMainContent()}
+
diff --git a/Data/Engine/web-interface/src/Assemblies/Assembly_Editor.jsx b/Data/Engine/web-interface/src/Assemblies/Assembly_Editor.jsx
index 0f2b40e5..7e87f8c8 100644
--- a/Data/Engine/web-interface/src/Assemblies/Assembly_Editor.jsx
+++ b/Data/Engine/web-interface/src/Assemblies/Assembly_Editor.jsx
@@ -17,7 +17,12 @@ import {
DialogActions,
ListItemText
} from "@mui/material";
-import { Add as AddIcon, Delete as DeleteIcon, UploadFile as UploadFileIcon } from "@mui/icons-material";
+import {
+ Add as AddIcon,
+ Delete as DeleteIcon,
+ UploadFile as UploadFileIcon,
+ Code as CodeIcon,
+} from "@mui/icons-material";
import Prism from "prismjs";
import "prismjs/components/prism-yaml";
import "prismjs/components/prism-bash";
@@ -119,6 +124,12 @@ const SECTION_CARD_SX = {
border: "1px solid #262f3d",
};
+const PAGE_ICON = CodeIcon;
+const PAGE_TITLE_SCRIPT = "Assembly Editor";
+const PAGE_TITLE_ANSIBLE = "Ansible Assembly Editor";
+const PAGE_SUBTITLE_SCRIPT = "Edit Borealis script assemblies, variables, and payloads before scheduling.";
+const PAGE_SUBTITLE_ANSIBLE = "Author Ansible playbooks with Borealis variables, inventory, and credential bindings.";
+
const MENU_PROPS = {
PaperProps: {
sx: {
@@ -341,6 +352,7 @@ export default function AssemblyEditor({
onConsumeInitialData,
onSaved,
userRole = "User",
+ onPageMetaChange,
}) {
const normalizedMode = mode === "ansible" ? "ansible" : "script";
const isAnsible = normalizedMode === "ansible";
@@ -365,6 +377,24 @@ export default function AssemblyEditor({
const [errorMessage, setErrorMessage] = useState("");
const isAdmin = (userRole || "").toLowerCase() === "admin";
+ const pageTitle = useMemo(
+ () => (isAnsible ? PAGE_TITLE_ANSIBLE : PAGE_TITLE_SCRIPT),
+ [isAnsible]
+ );
+ const pageSubtitle = useMemo(
+ () => (isAnsible ? PAGE_SUBTITLE_ANSIBLE : PAGE_SUBTITLE_SCRIPT),
+ [isAnsible]
+ );
+
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: pageTitle,
+ page_subtitle: pageSubtitle,
+ page_icon: PAGE_ICON,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange, pageSubtitle, pageTitle]);
+
const TYPE_OPTIONS = useMemo(
() => (isAnsible ? TYPE_OPTIONS_ALL.filter((o) => o.key === "ansible") : TYPE_OPTIONS_ALL.filter((o) => o.key !== "ansible")),
[isAnsible]
diff --git a/Data/Engine/web-interface/src/Assemblies/Assembly_List.jsx b/Data/Engine/web-interface/src/Assemblies/Assembly_List.jsx
index 6db24d21..15f89f98 100644
--- a/Data/Engine/web-interface/src/Assemblies/Assembly_List.jsx
+++ b/Data/Engine/web-interface/src/Assemblies/Assembly_List.jsx
@@ -232,7 +232,7 @@ const normalizeRow = (item, queueEntry) => {
};
};
-export default function AssemblyList({ onOpenWorkflow, onOpenScript, userRole = "User" }) {
+export default function AssemblyList({ onOpenWorkflow, onOpenScript, userRole = "User", onPageMetaChange }) {
const gridRef = useRef(null);
const [rows, setRows] = useState([]);
const [loading, setLoading] = useState(false);
@@ -253,6 +253,15 @@ export default function AssemblyList({ onOpenWorkflow, onOpenScript, userRole =
const [cloneDialog, setCloneDialog] = useState({ open: false, row: null, targetDomain: "user" });
const isAdmin = (userRole || "").toLowerCase() === "admin";
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: "Assemblies",
+ page_subtitle: "Collections of scripts, workflows, and playbooks used to automate tasks across devices.",
+ page_icon: AppsIcon,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange]);
+
const fetchAssemblies = useCallback(async () => {
setLoading(true);
setError("");
@@ -602,18 +611,7 @@ export default function AssemblyList({ onOpenWorkflow, onOpenScript, userRole =
}}
elevation={0}
>
-
-
-
-
- Assemblies
-
-
-
- Collections of scripts, workflows, and playbooks used to automate tasks across devices.
-
-
-
+
{
return status.toLowerCase();
};
-export default function DeviceApprovals() {
+export default function DeviceApprovals({ onPageMetaChange }) {
const [approvals, setApprovals] = useState([]);
const [statusFilter, setStatusFilter] = useState("all");
const [loading, setLoading] = useState(false);
@@ -100,6 +100,7 @@ export default function DeviceApprovals() {
const [actioningId, setActioningId] = useState(null);
const [conflictPrompt, setConflictPrompt] = useState(null);
const gridRef = useRef(null);
+ const useGlobalHeader = Boolean(onPageMetaChange);
const loadApprovals = useCallback(async () => {
setLoading(true);
@@ -122,6 +123,15 @@ export default function DeviceApprovals() {
useEffect(() => { loadApprovals(); }, [loadApprovals]);
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: "Device Approval Queue",
+ page_subtitle: "Review pending device enrollments and resolve conflicts with existing records.",
+ page_icon: SecurityIcon,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange]);
+
const dedupedApprovals = useMemo(() => {
const normalized = approvals
.map((record) => ({ ...record, status: normalizeStatus(record.status) }))
@@ -414,22 +424,49 @@ export default function DeviceApprovals() {
minWidth: 0,
height: "100%",
borderRadius: 0,
- border: `1px solid ${MAGIC_UI.panelBorder}`,
+ border: "none",
background: "transparent",
- boxShadow: "0 25px 80px rgba(6, 12, 30, 0.8)",
+ boxShadow: "none",
overflow: "hidden",
}}
elevation={0}
>
- {/* Page header (no solid backdrop so gradient reaches the top) */}
-
-
-
-
-
- Device Approval Queue
-
+ {!useGlobalHeader && (
+
+
+
+
+
+ Device Approval Queue
+
+
+
+
+ Status
+
+
+ } onClick={loadApprovals} disabled={loading}>
+ Refresh
+
+
+
+ )}
+
+ {/* Filters under shared header */}
+ {useGlobalHeader && (
+
Status
@@ -450,8 +487,8 @@ export default function DeviceApprovals() {
Refresh
-
-
+
+ )}
{/* Feedback */}
{feedback && (
diff --git a/Data/Engine/web-interface/src/Devices/Device_Details.jsx b/Data/Engine/web-interface/src/Devices/Device_Details.jsx
index 3c44d31d..716eb442 100644
--- a/Data/Engine/web-interface/src/Devices/Device_Details.jsx
+++ b/Data/Engine/web-interface/src/Devices/Device_Details.jsx
@@ -49,6 +49,8 @@ const MAGIC_UI = {
accentC: "#34d399",
};
+const PAGE_ICON = DeveloperBoardRoundedIcon;
+
const TAB_HOVER_GRADIENT = "linear-gradient(120deg, rgba(125,211,252,0.18), rgba(192,132,252,0.22))";
const gridFontFamily = '"IBM Plex Sans", "Helvetica Neue", Arial, sans-serif';
@@ -249,7 +251,7 @@ const GRID_COMPONENTS = {
HistoryActionsCell,
};
-export default function DeviceDetails({ device, onBack, onQuickJobLaunch }) {
+export default function DeviceDetails({ device, onBack, onQuickJobLaunch, onPageMetaChange }) {
const [tab, setTab] = useState(0);
const [agent, setAgent] = useState(device || {});
const [details, setDetails] = useState({});
@@ -956,7 +958,7 @@ export default function DeviceDetails({ device, onBack, onQuickJobLaunch }) {
sx={{
p: 2,
borderRadius: 3,
- border: `1px solid ${MAGIC_UI.panelBorder}`,
+ border: "none",
background: 'transparent',
boxShadow: "0 18px 40px rgba(2,6,23,0.55)",
mb: 1.5,
@@ -1494,6 +1496,26 @@ export default function DeviceDetails({ device, onBack, onQuickJobLaunch }) {
const status = lockedStatus || statusFromHeartbeat(agent.last_seen || device?.lastSeen);
+ const displayHostname = meta.hostname || summary.hostname || agent.hostname || device?.hostname || "Device Details";
+ const guidForSubtitle =
+ meta.agentGuid || summary.agent_guid || device?.agent_guid || device?.guid || device?.agentGuid || "";
+ const osLabel =
+ meta.operatingSystem || summary.operating_system || agent.agent_operating_system || agent.operating_system || "";
+ const subtitleParts = [];
+ if (status) subtitleParts.push(`Status: ${status}`);
+ if (osLabel) subtitleParts.push(osLabel);
+ if (guidForSubtitle) subtitleParts.push(`GUID ${guidForSubtitle}`);
+ const pageSubtitle = subtitleParts.join(" | ");
+
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: displayHostname,
+ page_subtitle: pageSubtitle,
+ page_icon: PAGE_ICON,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [displayHostname, onPageMetaChange, pageSubtitle]);
+
const topTabRenderers = [
renderDeviceSummaryTab,
renderStorageTab,
@@ -1512,7 +1534,7 @@ export default function DeviceDetails({ device, onBack, onQuickJobLaunch }) {
borderRadius: 0,
background: "transparent",
border: `1px solid ${MAGIC_UI.panelBorder}`,
- boxShadow: MAGIC_UI.glow,
+ boxShadow: "none",
display: "flex",
flexDirection: "column",
flexGrow: 1,
@@ -1529,7 +1551,7 @@ export default function DeviceDetails({ device, onBack, onQuickJobLaunch }) {
gap: 2,
}}
>
-
+
{onBack && (
)}
-
-
-
- {agent.hostname || "Device Details"}
-
-
- GUID: {meta.agentGuid || summary.agent_guid || "unknown"}
-
-
+
+
+ GUID: {meta.agentGuid || summary.agent_guid || "unknown"}
+
{
+ onPageMetaChange?.({
+ page_title: computedTitle,
+ page_subtitle: heroSubtitle,
+ page_icon: PAGE_ICON,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [computedTitle, heroSubtitle, onPageMetaChange]);
+
const fetchDevices = useCallback(async (options = {}) => {
const { refreshRepo = false } = options || {};
let repoSha = repoHash;
@@ -1577,16 +1590,21 @@ export default function DeviceList({
minWidth: 0,
height: "100%",
borderRadius: 0,
- border: `1px solid ${MAGIC_UI.panelBorder}`,
+ border: "none",
background: "transparent",
- boxShadow: MAGIC_UI.glow,
+ boxShadow: "none",
position: "relative",
overflow: "hidden",
}}
elevation={0}
>
-
-
-
- {computedTitle}
-
- {heroSubtitle}
-
- {hasActiveFilters ? (
-
- Filters
-
- {activeFilterCount}
-
-
- ) : null}
- {selectedIds.size > 0 ? (
-
- Selected
-
- {selectedIds.size}
-
-
- ) : null}
-
+
+ {hasActiveFilters ? (
+
+ Filters
+
+ {activeFilterCount}
+
+
+ ) : null}
+ {selectedIds.size > 0 ? (
+
+ Selected
+
+ {selectedIds.size}
+
+
+ ) : null}
diff --git a/Data/Engine/web-interface/src/Devices/Filters/Filter_List.jsx b/Data/Engine/web-interface/src/Devices/Filters/Filter_List.jsx
index cdac15e1..c6ce7a5e 100644
--- a/Data/Engine/web-interface/src/Devices/Filters/Filter_List.jsx
+++ b/Data/Engine/web-interface/src/Devices/Filters/Filter_List.jsx
@@ -38,6 +38,11 @@ const AURORA_SHELL = {
accent: "#7dd3fc",
};
+const PAGE_TITLE = "Device Filters";
+const PAGE_SUBTITLE =
+ "Build reusable filter definitions to target devices and assemblies without per-site duplication.";
+const PAGE_ICON = HeaderIcon;
+
const gradientButtonSx = {
backgroundImage: "linear-gradient(135deg,#7dd3fc,#c084fc)",
color: "#0b1220",
@@ -157,7 +162,7 @@ function normalizeFilters(raw) {
}));
}
-export default function DeviceFilterList({ onCreateFilter, onEditFilter, refreshToken }) {
+export default function DeviceFilterList({ onCreateFilter, onEditFilter, refreshToken, onPageMetaChange }) {
const gridRef = useRef(null);
const [rows, setRows] = useState([]);
const [loading, setLoading] = useState(false);
@@ -188,6 +193,15 @@ export default function DeviceFilterList({ onCreateFilter, onEditFilter, refresh
}
}, []);
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: PAGE_TITLE,
+ page_subtitle: PAGE_SUBTITLE,
+ page_icon: PAGE_ICON,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange]);
+
useEffect(() => {
loadFilters();
}, [loadFilters, refreshToken]);
@@ -328,32 +342,7 @@ export default function DeviceFilterList({ onCreateFilter, onEditFilter, refresh
overflow: "hidden",
}}
>
-
-
-
-
-
-
-
- Device Filters
-
-
- Build reusable filter definitions to target devices and assemblies without per-site duplication.
-
-
-
-
+
{
+ const IconComponent = meta.icon || LanIcon;
+ onPageMetaChange?.({
+ page_title: pageTitle,
+ page_subtitle: pageSubtitle,
+ page_icon: IconComponent,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [meta.icon, onPageMetaChange, pageSubtitle, pageTitle]);
+
const sortedRows = useMemo(() => {
const list = [...rows];
list.sort((a, b) => {
@@ -229,141 +295,214 @@ export default function SSHDevices({ type = "ssh" }) {
}
};
+ const accentColor = type === "winrm" ? MAGIC_UI.accentB : MAGIC_UI.accentA;
+ const HeaderIconComponent = meta.icon || LanIcon;
+
return (
-
+
-
-
- {pageTitle}
-
-
- {descriptionText}
-
-
-
- }
- sx={{ color: "#58a6ff", borderColor: "#58a6ff" }}
- onClick={loadDevices}
- disabled={loading}
- >
- Refresh
-
+
+
+
+
+
+
+
+
+
}
- sx={{ bgcolor: "#58a6ff", color: "#0b0f19" }}
+ sx={gradientButtonSx}
onClick={openCreate}
>
{addButtonLabel}
-
+
{error && (
-
- {error}
+
+
+ {error}
+
)}
{loading && (
-
-
- {loadingLabel}
+
+
+
+ {loadingLabel}
+
)}
-
-
-
-
-
- Hostname
-
-
-
-
- {addressLabel}
-
-
-
-
- Description
-
-
-
-
- Added
-
-
- Actions
-
-
-
- {sortedRows.map((row) => {
- const createdTs = Number(row.created_at || 0) * 1000;
- const createdDisplay = createdTs
- ? new Date(createdTs).toLocaleString()
- : (row.summary?.created || "");
- return (
-
- {row.hostname}
- {row.connection_endpoint || ""}
- {row.description || ""}
- {createdDisplay}
-
- openEdit(row)}>
-
-
- setDeleteTarget(row)}>
-
-
+
+
+
+
+
+
+
+ Hostname
+
+
+
+ {addressLabel}
+
+
+
+
+ Description
+
+
+
+
+ Added
+
+
+ Actions
- );
- })}
- {!sortedRows.length && !loading && (
-
-
- {emptyLabel}
-
-
- )}
-
-
+
+
+ {sortedRows.map((row) => {
+ const createdTs = Number(row.created_at || 0) * 1000;
+ const createdDisplay = createdTs
+ ? new Date(createdTs).toLocaleString()
+ : row.summary?.created || "";
+ return (
+
+ {row.hostname}
+ {row.connection_endpoint || ""}
+ {row.description || ""}
+ {createdDisplay}
+
+ openEdit(row)}
+ >
+
+
+ setDeleteTarget(row)}
+ >
+
+
+
+
+ );
+ })}
+ {!sortedRows.length && !loading && (
+
+
+ {emptyLabel}
+
+
+ )}
+
+
+
+
-
-
- {/* Subtitle directly under title (muted color, small size) */}
-
- List of automation jobs with schedules, results, and actions.
-
+
+
+
+
+
+
+
+
+
+
+ } sx={gradientButtonSx} onClick={() => onCreateJob && onCreateJob()}>
+ Create Job
+
+
+
+
+
+ }
+ sx={{
+ borderColor: "rgba(148,163,184,0.35)",
+ color: "#e2e8f0",
+ textTransform: "none",
+ borderRadius: 999,
+ px: 1.7,
+ minWidth: 86,
+ "&:hover": { borderColor: "rgba(148,163,184,0.55)" },
+ }}
+ >
+ Settings
+
+
+
- {/* Content area — a bit more top space below subtitle */}
-
+
new Set());
const [createOpen, setCreateOpen] = useState(false);
@@ -75,6 +79,15 @@ export default function SiteList({ onOpenDevicesForSite }) {
const [rotatingId, setRotatingId] = useState(null);
const gridRef = useRef(null);
+ useEffect(() => {
+ onPageMetaChange?.({
+ page_title: PAGE_TITLE,
+ page_subtitle: PAGE_SUBTITLE,
+ page_icon: PAGE_ICON,
+ });
+ return () => onPageMetaChange?.(null);
+ }, [onPageMetaChange]);
+
const fetchSites = useCallback(async () => {
try {
const res = await fetch("/api/sites");
@@ -216,31 +229,19 @@ export default function SiteList({ onOpenDevicesForSite }) {
minWidth: 0,
height: "100%",
borderRadius: 0,
- border: `1px solid ${MAGIC_UI.panelBorder}`,
+ border: "none",
background: "transparent",
- boxShadow: "0 25px 80px rgba(6, 12, 30, 0.8)",
+ boxShadow: "none",
overflow: "hidden",
}}
elevation={0}
>
- {/* Hero Section Removed — integrated header and buttons */}
-
-
-
-
-
- Managed Sites
+
+ {heroStats.selected > 0 ? (
+
+ {heroStats.selected} selected
-
-
- {`Monitoring ${heroStats.totalDevices} devices across ${heroStats.totalSites} site(s)`}
-
- {heroStats.selected > 0 && (
-
- {heroStats.selected} selected
-
- )}
-
+ ) : null}
} sx={RAINBOW_BUTTON_SX} onClick={() => setCreateOpen(true)}>
Create Site
diff --git a/Docs/Codex/USER_INTERFACE.md b/Docs/Codex/USER_INTERFACE.md
index 66e3b0c9..5779fa1a 100644
--- a/Docs/Codex/USER_INTERFACE.md
+++ b/Docs/Codex/USER_INTERFACE.md
@@ -5,12 +5,11 @@ Applies to all Borealis frontends. Use `Data/Engine/web-interface/src/Admin/Page
## Page Template Reference
- Purpose: visual-only baseline for new pages; copy structure but wire your data in real pages.
- Header: small Material icon left of the title, subtitle beneath, utility buttons on the top-right.
-- Shell: full-bleed aurora gradient container; avoid gutters on the Paper.
+- Shell: avoid gutters on the Paper.
- Selection column (for bulk actions): pinned left, square checkboxes, header checkbox enabled, ~52px fixed width, no menu/sort/resize; rely on AG Grid built-ins.
- Typography/buttons: IBM Plex Sans, gradient primary buttons, rounded corners (~8px), themed Quartz grid wrapper.
## MagicUI Styling Language (Visual System)
-- Aurora shells: gradient backgrounds blending deep navy (#040711) with soft cyan/violet blooms, subtle borders (`rgba(148,163,184,0.35)`), and low, velvety shadows.
- Full-bleed canvas: hero shells run edge-to-edge; inset padding lives inside cards so gradients feel immersive.
- Glass panels: glassmorphic layers (`rgba(15,23,42,0.7)`), rounded 16–24px corners, blurred backdrops, micro borders, optional radial flares for motion.
- Hero storytelling: start views with stat-forward heroes—gradient StatTiles (min 160px) and uppercase pills (HERO_BADGE_SX) summarizing live signals/filters.