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" }}>
-
-
-
-
- ))
- )}
-
-
+
+
+
+
+