From 9946c7ea2e9761881d8ac232bcb1dd6d73937ba4 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Thu, 16 Oct 2025 03:17:12 -0600 Subject: [PATCH] refactor credential list to ag grid --- .../src/Access_Management/Credential_List.jsx | 338 ++++++++++++------ 1 file changed, 225 insertions(+), 113 deletions(-) diff --git a/Data/Server/WebUI/src/Access_Management/Credential_List.jsx b/Data/Server/WebUI/src/Access_Management/Credential_List.jsx index 353e231..be0020a 100644 --- a/Data/Server/WebUI/src/Access_Management/Credential_List.jsx +++ b/Data/Server/WebUI/src/Access_Management/Credential_List.jsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Box, Button, @@ -6,12 +6,6 @@ import { Menu, MenuItem, Paper, - Table, - TableBody, - TableCell, - TableHead, - TableRow, - TableSortLabel, Typography, CircularProgress } from "@mui/material"; @@ -21,31 +15,31 @@ import RefreshIcon from "@mui/icons-material/Refresh"; import LockIcon from "@mui/icons-material/Lock"; import WifiIcon from "@mui/icons-material/Wifi"; import ComputerIcon from "@mui/icons-material/Computer"; +import { AgGridReact } from "ag-grid-react"; +import { ModuleRegistry, AllCommunityModule, themeQuartz } from "ag-grid-community"; import CredentialEditor from "./Credential_Editor.jsx"; import { ConfirmDeleteDialog } from "../Dialogs.jsx"; -const tablePaperSx = { m: 2, p: 0, bgcolor: "#1e1e1e", borderRadius: 2 }; -const tableSx = { - minWidth: 840, - "& th, & td": { - color: "#ddd", - borderColor: "#2a2a2a", - fontSize: 13, - py: 0.9 - }, - "& th .MuiTableSortLabel-root": { color: "#ddd" }, - "& th .MuiTableSortLabel-root.Mui-active": { color: "#ddd" } -}; +ModuleRegistry.registerModules([AllCommunityModule]); -const columns = [ - { id: "name", label: "Name" }, - { id: "credential_type", label: "Credential Type" }, - { id: "connection_type", label: "Connection" }, - { id: "site_name", label: "Site" }, - { id: "username", label: "Username" }, - { id: "updated_at", label: "Updated" }, - { id: "actions", label: "" } -]; +const myTheme = themeQuartz.withParams({ + accentColor: "#FFA6FF", + backgroundColor: "#1f2836", + browserColorScheme: "dark", + chromeBackgroundColor: { + ref: "foregroundColor", + mix: 0.07, + onto: "backgroundColor" + }, + fontFamily: { + googleFont: "IBM Plex Sans" + }, + foregroundColor: "#FFF", + headerFontSize: 14 +}); + +const themeClassName = myTheme.themeName || "ag-theme-quartz"; +const gridFontFamily = '"IBM Plex Sans", "Helvetica Neue", Arial, sans-serif'; function formatTs(ts) { if (!ts) return "-"; @@ -69,8 +63,6 @@ function connectionIcon(connection) { export default function CredentialList({ isAdmin = false }) { const [rows, setRows] = useState([]); - const [orderBy, setOrderBy] = useState("name"); - const [order, setOrder] = useState("asc"); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [menuAnchor, setMenuAnchor] = useState(null); @@ -80,18 +72,126 @@ export default function CredentialList({ isAdmin = false }) { const [editingCredential, setEditingCredential] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const [deleteBusy, setDeleteBusy] = useState(false); + const gridApiRef = useRef(null); - const sortedRows = useMemo(() => { - const sorted = [...rows]; - sorted.sort((a, b) => { - const aVal = (a?.[orderBy] ?? "").toString().toLowerCase(); - const bVal = (b?.[orderBy] ?? "").toString().toLowerCase(); - if (aVal < bVal) return order === "asc" ? -1 : 1; - if (aVal > bVal) return order === "asc" ? 1 : -1; - return 0; - }); - return sorted; - }, [rows, order, orderBy]); + const openMenu = useCallback((event, row) => { + setMenuAnchor(event.currentTarget); + setMenuRow(row); + }, []); + + const closeMenu = useCallback(() => { + setMenuAnchor(null); + setMenuRow(null); + }, []); + + const connectionCellRenderer = useCallback((params) => { + const row = params.data || {}; + const label = titleCase(row.connection_type); + return ( + + {connectionIcon(row.connection_type)} + + {label} + + + ); + }, []); + + const actionCellRenderer = useCallback( + (params) => { + const row = params.data; + if (!row) return null; + const handleClick = (event) => { + event.preventDefault(); + event.stopPropagation(); + openMenu(event, row); + }; + return ( + + + + ); + }, + [openMenu] + ); + + const columnDefs = useMemo( + () => [ + { + headerName: "Name", + field: "name", + sort: "asc", + cellRenderer: (params) => params.value || "-" + }, + { + headerName: "Credential Type", + field: "credential_type", + valueGetter: (params) => titleCase(params.data?.credential_type) + }, + { + headerName: "Connection", + field: "connection_type", + cellRenderer: connectionCellRenderer + }, + { + headerName: "Site", + field: "site_name", + cellRenderer: (params) => params.value || "-" + }, + { + headerName: "Username", + field: "username", + cellRenderer: (params) => params.value || "-" + }, + { + headerName: "Updated", + field: "updated_at", + valueGetter: (params) => + formatTs(params.data?.updated_at || params.data?.created_at) + }, + { + headerName: "", + field: "__actions__", + minWidth: 70, + maxWidth: 80, + sortable: false, + filter: false, + resizable: false, + suppressMenu: true, + cellRenderer: actionCellRenderer, + pinned: "right" + } + ], + [actionCellRenderer, connectionCellRenderer] + ); + + const defaultColDef = useMemo( + () => ({ + sortable: true, + filter: "agTextColumnFilter", + resizable: true, + flex: 1, + minWidth: 140, + cellStyle: { + display: "flex", + alignItems: "center", + color: "#f5f7fa", + fontFamily: gridFontFamily, + fontSize: "13px" + }, + headerClass: "credential-grid-header" + }), + [] + ); + + const getRowId = useCallback( + (params) => + params.data?.id || + params.data?.name || + params.data?.username || + String(params.rowIndex ?? ""), + [] + ); const fetchCredentials = useCallback(async () => { setLoading(true); @@ -115,25 +215,6 @@ export default function CredentialList({ isAdmin = false }) { fetchCredentials(); }, [fetchCredentials]); - const handleSort = (columnId) => () => { - if (orderBy === columnId) { - setOrder((prev) => (prev === "asc" ? "desc" : "asc")); - } else { - setOrderBy(columnId); - setOrder("asc"); - } - }; - - const openMenu = (event, row) => { - setMenuAnchor(event.currentTarget); - setMenuRow(row); - }; - - const closeMenu = () => { - setMenuAnchor(null); - setMenuRow(null); - }; - const handleCreate = () => { setEditorMode("create"); setEditingCredential(null); @@ -176,6 +257,22 @@ export default function CredentialList({ isAdmin = false }) { await fetchCredentials(); }; + const handleGridReady = useCallback((params) => { + gridApiRef.current = params.api; + }, []); + + useEffect(() => { + const api = gridApiRef.current; + if (!api) return; + if (loading) { + api.showLoadingOverlay(); + } else if (!rows.length) { + api.showNoRowsOverlay(); + } else { + api.hideOverlay(); + } + }, [loading, rows]); + if (!isAdmin) { return ( @@ -191,8 +288,29 @@ export default function CredentialList({ isAdmin = false }) { return ( <> - - + + Credentials @@ -224,66 +342,60 @@ export default function CredentialList({ isAdmin = false }) { {loading && ( - + Loading credentials… )} {error && ( - + {error} )} - - - - {columns.map((col) => ( - - {col.id === "actions" ? null : ( - - {col.label} - - )} - - ))} - - - - {!sortedRows.length && !loading ? ( - - - No credentials have been created yet. - - - ) : ( - sortedRows.map((row) => ( - - {row.name || "-"} - {titleCase(row.credential_type)} - - - {connectionIcon(row.connection_type)} - {titleCase(row.connection_type)} - - - {row.site_name || "-"} - {row.username || "-"} - {formatTs(row.updated_at || row.created_at)} - - openMenu(e, row)} sx={{ color: "#7db7ff" }}> - - - - - )) - )} - -
+ + + + +