From be0a03ab61d3d7c94a6eda86836629f62388ceed Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Fri, 7 Nov 2025 05:10:36 -0700 Subject: [PATCH] Redesigned Device Approval Page --- .../src/Devices/Device_Approvals.jsx | 488 +++++++++--------- 1 file changed, 255 insertions(+), 233 deletions(-) diff --git a/Data/Engine/web-interface/src/Devices/Device_Approvals.jsx b/Data/Engine/web-interface/src/Devices/Device_Approvals.jsx index 7f4c2c87..92209224 100644 --- a/Data/Engine/web-interface/src/Devices/Device_Approvals.jsx +++ b/Data/Engine/web-interface/src/Devices/Device_Approvals.jsx @@ -1,6 +1,4 @@ -////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: /Data/Server/WebUI/src/Admin/Device_Approvals.jsx - -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useState, useRef } from "react"; import { Alert, Box, @@ -13,18 +11,11 @@ import { DialogContentText, DialogTitle, FormControl, - IconButton, InputLabel, MenuItem, Paper, Select, Stack, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, TextField, Tooltip, Typography, @@ -35,6 +26,32 @@ import { Refresh as RefreshIcon, Security as SecurityIcon, } from "@mui/icons-material"; +import { AgGridReact } from "ag-grid-react"; +import { ModuleRegistry, AllCommunityModule, themeQuartz } from "ag-grid-community"; +// NOTE: Do NOT import global AG Grid CSS to avoid affecting other pages. +// We rely on the Quartz theme class name + scoped CSS vars like the rest of MagicUI. +ModuleRegistry.registerModules([AllCommunityModule]); + +// MagicUI palette to match Enrollment Codes / Site List +const MAGIC_UI = { + shellBg: + "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", + panelBorder: "rgba(148, 163, 184, 0.35)", + textBright: "#e2e8f0", + accentA: "#7dd3fc", +}; + +// Quartz theme instance (same params used across MagicUI pages) +const gridTheme = themeQuartz.withParams({ + accentColor: "#8b5cf6", + backgroundColor: "#070b1a", + browserColorScheme: "dark", + fontFamily: { googleFont: "IBM Plex Sans" }, + foregroundColor: "#f4f7ff", + headerFontSize: 13, +}); +const themeClassName = gridTheme.themeName || "ag-theme-quartz"; const STATUS_OPTIONS = [ { value: "all", label: "All" }, @@ -73,7 +90,7 @@ const normalizeStatus = (status) => { return status.toLowerCase(); }; -function DeviceApprovals() { +export default function DeviceApprovals() { const [approvals, setApprovals] = useState([]); const [statusFilter, setStatusFilter] = useState("all"); const [loading, setLoading] = useState(false); @@ -82,6 +99,7 @@ function DeviceApprovals() { const [guidInputs, setGuidInputs] = useState({}); const [actioningId, setActioningId] = useState(null); const [conflictPrompt, setConflictPrompt] = useState(null); + const gridRef = useRef(null); const loadApprovals = useCallback(async () => { setLoading(true); @@ -102,9 +120,7 @@ function DeviceApprovals() { } }, [statusFilter]); - useEffect(() => { - loadApprovals(); - }, [loadApprovals]); + useEffect(() => { loadApprovals(); }, [loadApprovals]); const dedupedApprovals = useMemo(() => { const normalized = approvals @@ -183,13 +199,9 @@ function DeviceApprovals() { } const appliedResolution = (body.conflict_resolution || payload.conflict_resolution || "").toLowerCase(); let successMessage = "Enrollment approved"; - if (appliedResolution === "overwrite") { - successMessage = "Enrollment approved; existing device overwritten"; - } else if (appliedResolution === "coexist") { - successMessage = "Enrollment approved; devices will co-exist"; - } else if (appliedResolution === "auto_merge_fingerprint") { - successMessage = "Enrollment approved; device reconnected with its existing identity"; - } + if (appliedResolution === "overwrite") successMessage = "Enrollment approved; existing device overwritten"; + else if (appliedResolution === "coexist") successMessage = "Enrollment approved; devices will co-exist"; + else if (appliedResolution === "auto_merge_fingerprint") successMessage = "Enrollment approved; device reconnected with its existing identity"; setFeedback({ type: "success", message: successMessage }); await loadApprovals(); } catch (err) { @@ -213,11 +225,7 @@ function DeviceApprovals() { const fallbackAlternate = record.alternate_hostname || (record.hostname_claimed ? `${record.hostname_claimed}-1` : ""); - setConflictPrompt({ - record, - conflict, - alternate: fallbackAlternate || "", - }); + setConflictPrompt({ record, conflict, alternate: fallbackAlternate || "" }); return; } submitApproval(record); @@ -225,50 +233,6 @@ function DeviceApprovals() { [guidInputs, submitApproval] ); - const handleConflictCancel = useCallback(() => { - setConflictPrompt(null); - }, []); - - const handleConflictOverwrite = useCallback(() => { - if (!conflictPrompt?.record) { - setConflictPrompt(null); - return; - } - const { record, conflict } = conflictPrompt; - setConflictPrompt(null); - const conflictGuid = conflict?.guid != null ? String(conflict.guid).trim() : ""; - submitApproval(record, { - guid: conflictGuid, - conflictResolution: "overwrite", - }); - }, [conflictPrompt, submitApproval]); - - const handleConflictCoexist = useCallback(() => { - if (!conflictPrompt?.record) { - setConflictPrompt(null); - return; - } - const { record } = conflictPrompt; - setConflictPrompt(null); - submitApproval(record, { - conflictResolution: "coexist", - }); - }, [conflictPrompt, submitApproval]); - - const conflictRecord = conflictPrompt?.record; - const conflictInfo = conflictPrompt?.conflict; - const conflictHostname = conflictRecord?.hostname_claimed || conflictRecord?.hostname || ""; - const conflictSiteName = conflictInfo?.site_name || ""; - const conflictSiteDescriptor = conflictInfo - ? conflictSiteName - ? `under site ${conflictSiteName}` - : "under site (not assigned)" - : "under site (not assigned)"; - const conflictAlternate = - conflictPrompt?.alternate || - (conflictHostname ? `${conflictHostname}-1` : "hostname-1"); - const conflictGuidDisplay = conflictInfo?.guid || ""; - const handleDeny = useCallback( async (record) => { if (!record?.id) return; @@ -297,171 +261,236 @@ function DeviceApprovals() { [loadApprovals] ); + const columns = useMemo(() => [ + { + headerName: "Status", + field: "status", + valueGetter: (p) => normalizeStatus(p.data?.status), + cellRenderer: (params) => { + const status = params.value || "pending"; + // mimic MUI Chip coloring via text hues + const color = status === "completed" ? "#34d399" + : status === "approved" ? "#60a5fa" + : status === "denied" || status === "expired" ? "#9aa0a6" + : "#fbbf24"; + return {status}; + }, + width: 120, + }, + { headerName: "Hostname", field: "hostname_claimed", minWidth: 180 }, + { + headerName: "Fingerprint", + field: "ssl_key_fingerprint_claimed", + valueFormatter: (p) => formatFingerprint(p.value), + cellStyle: { fontFamily: "monospace", whiteSpace: "nowrap" }, + minWidth: 200, + }, + { + headerName: "Enrollment Code", + field: "enrollment_code_id", + cellStyle: { fontFamily: "monospace" }, + minWidth: 140, + }, + { headerName: "Created", field: "created_at", valueFormatter: (p) => formatDateTime(p.value), minWidth: 160 }, + { headerName: "Updated", field: "updated_at", valueFormatter: (p) => formatDateTime(p.value), minWidth: 160 }, + { + headerName: "Approved By", + valueGetter: (p) => p.data?.approved_by_username || p.data?.approved_by_user_id || "—", + minWidth: 140, + }, + { + headerName: "Actions", + cellRenderer: (params) => { + const record = params.data || {}; + const status = normalizeStatus(record.status); + const showActions = status === "pending"; + const guidValue = params.context.guidInputs[record.id] || ""; + const { startApprove, handleDeny, handleGuidChange, actioningId } = params.context; + if (!showActions) { + return No actions available; + } + const isBusy = actioningId === record.id; + return ( + + handleGuidChange(record.id, e.target.value)} + sx={{ minWidth: 220 }} + /> + + + + + + + + + + + + + + ); + }, + minWidth: 480, + flex: 1, + }, + ], []); + + const defaultColDef = useMemo(() => ({ + sortable: true, + filter: true, + resizable: true, + minWidth: 140, + }), []); + + // Dialog helpers + const conflictRecord = conflictPrompt?.record; + const conflictInfo = conflictPrompt?.conflict; + const conflictHostname = conflictRecord?.hostname_claimed || conflictRecord?.hostname || ""; + const conflictSiteName = conflictInfo?.site_name || ""; + const conflictSiteDescriptor = conflictInfo + ? conflictSiteName + ? `under site ${conflictSiteName}` + : "under site (not assigned)" + : "under site (not assigned)"; + const conflictAlternate = + conflictPrompt?.alternate || + (conflictHostname ? `${conflictHostname}-1` : "hostname-1"); + const conflictGuidDisplay = conflictInfo?.guid || ""; + + const handleConflictCancel = useCallback(() => setConflictPrompt(null), []); + const handleConflictOverwrite = useCallback(() => { + if (!conflictPrompt?.record) { setConflictPrompt(null); return; } + const { record, conflict } = conflictPrompt; + setConflictPrompt(null); + const conflictGuid = conflict?.guid != null ? String(conflict.guid).trim() : ""; + submitApproval(record, { guid: conflictGuid, conflictResolution: "overwrite" }); + }, [conflictPrompt, submitApproval]); + const handleConflictCoexist = useCallback(() => { + if (!conflictPrompt?.record) { setConflictPrompt(null); return; } + const { record } = conflictPrompt; + setConflictPrompt(null); + submitApproval(record, { conflictResolution: "coexist" }); + }, [conflictPrompt, submitApproval]); + return ( - - - - Device Approval Queue - - - - - - Status - - - - + + {/* Page header (no solid backdrop so gradient reaches the top) */} + + + + + + Device Approval Queue + + + + + Status + + + + + - {feedback ? ( + {/* Feedback */} + {feedback && ( + setFeedback(null)}> {feedback.message} - ) : null} - - {error ? ( + + )} + {error && ( + {error} - ) : null} + + )} - - - - - Status - Hostname - Fingerprint - Enrollment Code - Created - Updated - Approved By - Actions - - - - {loading ? ( - - - - - Loading approvals… - - - - ) : dedupedApprovals.length === 0 ? ( - - - - No enrollment requests match this filter. - - - - ) : ( - dedupedApprovals.map((record) => { - const status = normalizeStatus(record.status); - const showActions = status === "pending"; - const guidValue = guidInputs[record.id] || ""; - const approverDisplay = record.approved_by_username || record.approved_by_user_id; - return ( - - - - - {record.hostname_claimed || "—"} - - {formatFingerprint(record.ssl_key_fingerprint_claimed)} - - - {record.enrollment_code_id || "—"} - - {formatDateTime(record.created_at)} - {formatDateTime(record.updated_at)} - {approverDisplay || "—"} - - {showActions ? ( - - handleGuidChange(record.id, event.target.value)} - sx={{ minWidth: 200 }} - /> - - - - startApprove(record)} - disabled={actioningId === record.id} - > - {actioningId === record.id ? ( - - ) : ( - - )} - - - - - - handleDeny(record)} - disabled={actioningId === record.id} - > - - - - - - - ) : ( - - No actions available - - )} - - - ); - }) - )} - -
-
-
- + +
+ + {/* Conflict Dialog (unchanged logic) */} + Hostname Conflict @@ -488,18 +517,11 @@ function DeviceApprovals() { - - + ); } - -export default React.memo(DeviceApprovals);