From d05877a08f0fbc944a78a8a0575d2f31acebcbe6 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Wed, 26 Nov 2025 06:36:03 -0700 Subject: [PATCH] Standardized Page Title, Subtitle, and Icon Across All Borealis Pages --- .../Access_Management/Credential_Editor.jsx | 172 ++++--- .../src/Access_Management/Credential_List.jsx | 57 ++- .../Access_Management/Github_API_Token.jsx | 101 +++-- .../src/Access_Management/Users.jsx | 44 +- .../src/Admin/Log_Management.jsx | 89 ++-- .../web-interface/src/Admin/Page_Template.jsx | 129 +++--- .../web-interface/src/Admin/Server_Info.jsx | 49 +- Data/Engine/web-interface/src/App.jsx | 98 +++- .../src/Assemblies/Assembly_Editor.jsx | 32 +- .../src/Assemblies/Assembly_List.jsx | 24 +- .../src/Devices/Device_Approvals.jsx | 63 ++- .../src/Devices/Device_Details.jsx | 64 +-- .../web-interface/src/Devices/Device_List.jsx | 82 ++-- .../src/Devices/Filters/Filter_List.jsx | 43 +- .../web-interface/src/Devices/SSH_Devices.jsx | 423 ++++++++++++------ .../src/Scheduling/Create_Job.jsx | 55 ++- .../src/Scheduling/Scheduled_Jobs_List.jsx | 112 +++-- .../web-interface/src/Sites/Site_List.jsx | 41 +- Docs/Codex/USER_INTERFACE.md | 3 +- 19 files changed, 1060 insertions(+), 621 deletions(-) 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 ( - {title} - + + + {title} + + {fetchingDetail && ( - - + + Loading credential details… )} {error && ( - - {error} + + {error} )} - Site + Site - Credential Type + Credential Type - Connection + Connection {isEdit && currentCredentialFlags.hasPrivateKey && !privateKeyDirty && !clearPrivateKey && ( - setClearPrivateKey(true)} sx={{ color: "#ff8080" }}> + setClearPrivateKey(true)} sx={{ color: MAGIC_UI.danger }}> @@ -445,7 +502,7 @@ export default function CredentialEditor({ Private key is stored. Upload or paste a new one to replace, or clear it. )} {clearPrivateKey && ( - Private key will be removed when saving. + Private key will be removed when saving. )} @@ -455,15 +512,11 @@ export default function CredentialEditor({ value={form.private_key_passphrase} onChange={updateField("private_key_passphrase")} disabled={disableSave} - sx={{ - flex: 1, - "& .MuiInputBase-root": { bgcolor: "#1f1f1f", color: "#fff" }, - "& label": { color: "#888" } - }} + sx={{ flex: 1, ...INPUT_SX }} /> {isEdit && currentCredentialFlags.hasPrivateKeyPassphrase && !passphraseDirty && !clearPassphrase && ( - setClearPassphrase(true)} sx={{ color: "#ff8080" }}> + setClearPassphrase(true)} sx={{ color: MAGIC_UI.danger }}> @@ -473,17 +526,17 @@ export default function CredentialEditor({ A passphrase is stored for this key. )} {clearPassphrase && ( - Key passphrase will be removed when saving. + Key passphrase will be removed when saving. )} - Privilege Escalation + Privilege Escalation setStatusFilter(e.target.value)} + > + {STATUS_OPTIONS.map((option) => ( + + {option.label} + + ))} + + + + + + )} + + {/* 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 && ( + + + + + + + + + - + {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} + + + )} + + +
+
{isEdit ? editDialogTitle : newDialogTitle} @@ -376,12 +515,13 @@ export default function SSHDevices({ type = "ssh" }) { size="small" sx={{ "& .MuiOutlinedInput-root": { - backgroundColor: "#1f1f1f", - color: "#fff", - "& fieldset": { borderColor: "#555" }, - "&:hover fieldset": { borderColor: "#888" } + backgroundColor: "rgba(12,18,35,0.75)", + color: MAGIC_UI.textBright, + "& fieldset": { borderColor: MAGIC_UI.panelBorder }, + "&:hover fieldset": { borderColor: accentColor }, + "&.Mui-focused fieldset": { borderColor: accentColor }, }, - "& .MuiInputLabel-root": { color: "#aaa" } + "& .MuiInputLabel-root": { color: MAGIC_UI.textMuted }, }} helperText="Hostname used within Borealis (unique)." /> @@ -393,12 +533,13 @@ export default function SSHDevices({ type = "ssh" }) { size="small" sx={{ "& .MuiOutlinedInput-root": { - backgroundColor: "#1f1f1f", - color: "#fff", - "& fieldset": { borderColor: "#555" }, - "&:hover fieldset": { borderColor: "#888" } + backgroundColor: "rgba(12,18,35,0.75)", + color: MAGIC_UI.textBright, + "& fieldset": { borderColor: MAGIC_UI.panelBorder }, + "&:hover fieldset": { borderColor: accentColor }, + "&.Mui-focused fieldset": { borderColor: accentColor }, }, - "& .MuiInputLabel-root": { color: "#aaa" } + "& .MuiInputLabel-root": { color: MAGIC_UI.textMuted }, }} helperText={`IP or FQDN Borealis can reach over ${typeLabel}.`} /> @@ -410,12 +551,13 @@ export default function SSHDevices({ type = "ssh" }) { size="small" sx={{ "& .MuiOutlinedInput-root": { - backgroundColor: "#1f1f1f", - color: "#fff", - "& fieldset": { borderColor: "#555" }, - "&:hover fieldset": { borderColor: "#888" } + backgroundColor: "rgba(12,18,35,0.75)", + color: MAGIC_UI.textBright, + "& fieldset": { borderColor: MAGIC_UI.panelBorder }, + "&:hover fieldset": { borderColor: accentColor }, + "&.Mui-focused fieldset": { borderColor: accentColor }, }, - "& .MuiInputLabel-root": { color: "#aaa" } + "& .MuiInputLabel-root": { color: MAGIC_UI.textMuted }, }} /> {error && ( - + {error} )} - - - - - - - - - - - - {/* Subtitle directly under title (muted color, small size) */} - - List of automation jobs with schedules, results, and actions. - + + + + + + + + + + + + + + + + + + - {/* 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}