From a9150982de38dde6dffc12608c620339d25ec7f3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 22 Jan 2025 22:12:36 -0500 Subject: [PATCH 1/3] Update index.jsx --- src/pages/identity/administration/users/user/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/identity/administration/users/user/index.jsx b/src/pages/identity/administration/users/user/index.jsx index 3ed348320120..80cbef9983e4 100644 --- a/src/pages/identity/administration/users/user/index.jsx +++ b/src/pages/identity/administration/users/user/index.jsx @@ -18,6 +18,7 @@ import { useEffect, useState } from "react"; import { usePopover } from "../../../../../hooks/use-popover"; import { useDialog } from "../../../../../hooks/use-dialog"; import CippUserActions from "/src/components/CippComponents/CippUserActions"; +import { PencilIcon } from "@heroicons/react/24/outline"; const Page = () => { const popover = usePopover(); @@ -362,6 +363,7 @@ const Page = () => { hideTitle: true, actions: [ { + icon: , label: "Edit Group", link: "/identity/administration/groups/edit?groupId=[id]", }, From be59621813671dd41802cab0d1eec1a605b40a35 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 23 Jan 2025 13:44:56 -0500 Subject: [PATCH 2/3] Update index.jsx --- src/pages/identity/administration/users/user/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/identity/administration/users/user/index.jsx b/src/pages/identity/administration/users/user/index.jsx index 80cbef9983e4..37f3d396924f 100644 --- a/src/pages/identity/administration/users/user/index.jsx +++ b/src/pages/identity/administration/users/user/index.jsx @@ -356,7 +356,7 @@ const Page = () => { cardLabelBox: { cardLabelBoxHeader: , }, - text: "Group Memberships", + text: "Groups", subtext: "List of groups the user is a member of", table: { title: "Group Memberships", @@ -384,10 +384,10 @@ const Page = () => { cardLabelBox: { cardLabelBoxHeader: , }, - text: "Roles", + text: "Admin Roles", subtext: "List of roles the user is a member of", table: { - title: "Role Memberships", + title: "Admin Roles", hideTitle: true, data: userMemberOf?.data?.Results.filter( (item) => item?.["@odata.type"] === "#microsoft.graph.directoryRole" From 9408080c0151c94dd1f36e5daff510da320e5cde Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 23 Jan 2025 13:46:27 -0500 Subject: [PATCH 3/3] table maintenance new superadmin page --- .../CippCards/CippPropertyListCard.jsx | 6 +- .../CippComponents/CippApiDialog.jsx | 17 +- src/layouts/config.js | 5 + src/pages/cipp/advanced/table-maintenance.js | 413 ++++++++++++++++++ 4 files changed, 436 insertions(+), 5 deletions(-) create mode 100644 src/pages/cipp/advanced/table-maintenance.js diff --git a/src/components/CippCards/CippPropertyListCard.jsx b/src/components/CippCards/CippPropertyListCard.jsx index b5952305fa79..ff7cdcd92a64 100644 --- a/src/components/CippCards/CippPropertyListCard.jsx +++ b/src/components/CippCards/CippPropertyListCard.jsx @@ -151,7 +151,11 @@ export const CippPropertyListCard = (props) => { action: item, ready: true, }); - createDialog.handleOpen(); + if (item?.noConfirm) { + item.customFunction(item, data, {}); + } else { + createDialog.handleOpen(); + } } } disabled={handleActionDisabled(data, item)} diff --git a/src/components/CippComponents/CippApiDialog.jsx b/src/components/CippComponents/CippApiDialog.jsx index 282d8bab7b3b..70423a417045 100644 --- a/src/components/CippComponents/CippApiDialog.jsx +++ b/src/components/CippComponents/CippApiDialog.jsx @@ -38,6 +38,9 @@ export const CippApiDialog = (props) => { bulkRequest: api.multiPost === false, onResult: (result) => { setPartialResults((prevResults) => [...prevResults, result]); + if (api?.onSuccess) { + api.onSuccess(result); + } }, }); const actionGetRequest = ApiGetCall({ @@ -50,6 +53,9 @@ export const CippApiDialog = (props) => { bulkRequest: api.multiPost === false, onResult: (result) => { setPartialResults((prevResults) => [...prevResults, result]); + if (api?.onSuccess) { + api.onSuccess(result); + } }, }); @@ -58,6 +64,8 @@ export const CippApiDialog = (props) => { return api.dataFunction(row); } var newData = {}; + console.log("the received row", row); + console.log("the received dataObject", dataObject); if (api?.postEntireRow) { newData = row; @@ -85,6 +93,7 @@ export const CippApiDialog = (props) => { } }); } + console.log("output", newData); return newData; }; const tenantFilter = useSettings().currentTenant; @@ -209,10 +218,10 @@ export const CippApiDialog = (props) => { } useEffect(() => { if (api.noConfirm) { - formHook.handleSubmit(onSubmit)(); - createDialog.handleClose(); + formHook.handleSubmit(onSubmit)(); // Submits the form on mount + createDialog.handleClose(); // Closes the dialog after submitting } - }, [api.noConfirm]); + }, [api.noConfirm]); // Run effect only when api.noConfirm changes const handleClose = () => { createDialog.handleClose(); @@ -251,7 +260,7 @@ export const CippApiDialog = (props) => { Close diff --git a/src/layouts/config.js b/src/layouts/config.js index 39d5f0c8714e..7dceb0c4cf65 100644 --- a/src/layouts/config.js +++ b/src/layouts/config.js @@ -472,6 +472,11 @@ export const nativeMenuItems = [ path: "/cipp/advanced/timers", roles: ["superadmin"], }, + { + title: "Table Maintenance", + path: "/cipp/advanced/table-maintenance", + roles: ["superadmin"], + } ], }, ], diff --git a/src/pages/cipp/advanced/table-maintenance.js b/src/pages/cipp/advanced/table-maintenance.js new file mode 100644 index 000000000000..d736115b1636 --- /dev/null +++ b/src/pages/cipp/advanced/table-maintenance.js @@ -0,0 +1,413 @@ +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import { useEffect, useState } from "react"; +import { ApiPostCall } from "../../../api/ApiCall"; +import { CippPropertyListCard } from "/src/components/CippCards/CippPropertyListCard"; // Fixed import +import { CippDataTable } from "/src/components/CippTable/CippDataTable"; // Fixed import +import { useDialog } from "../../../hooks/use-dialog"; +import { + Box, + Container, + Stack, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + IconButton, + Button, + SvgIcon, + Tooltip, + Typography, +} from "@mui/material"; +import { MagnifyingGlassIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; +import { Add, AddCircle, RemoveCircle, Sync, WarningAmber } from "@mui/icons-material"; +import CippFormComponent from "../../../components/CippComponents/CippFormComponent"; +import { useForm, useWatch } from "react-hook-form"; +import { CippApiDialog } from "../../../components/CippComponents/CippApiDialog"; +import { Grid } from "@mui/system"; + +const CustomAddEditRowDialog = ({ formControl, open, onClose, onSubmit, defaultValues }) => { + const fields = useWatch({ control: formControl.control, name: "fields" }); + + useEffect(() => { + if (open) { + console.log(defaultValues); + formControl.reset({ + fields: defaultValues.fields || [], + }); + } + }, [open, defaultValues]); + + const addField = () => { + formControl.reset({ + fields: [...fields, { name: "", value: "" }], + }); + }; + + const removeField = (index) => { + const newFields = fields.filter((_, i) => i !== index); + formControl.reset({ fields: newFields }); + }; + + return ( + + Add/Edit Row + + + {Array.isArray(fields) && fields?.length > 0 && ( + <> + {fields.map((field, index) => ( + + + + + + + + removeField(index)}> + + + + ))} + + )} + + + + + + + + + ); +}; + +const Page = () => { + const pageTitle = "Table Maintenance"; + const apiUrl = "/api/ExecAzBobbyTables"; + const [tables, setTables] = useState([]); + const [selectedTable, setSelectedTable] = useState(null); + const [tableData, setTableData] = useState([]); + const addTableDialog = useDialog(); // Add dialog for adding table + const deleteTableDialog = useDialog(); // Add dialog for deleting table + const addEditRowDialog = useDialog(); // Add dialog for adding/editing row + const [defaultAddEditValues, setDefaultAddEditValues] = useState({}); + const formControl = useForm({ + mode: "onChange", + }); + + const addEditFormControl = useForm({ + mode: "onChange", + }); + + const tableFilter = useWatch({ control: formControl.control, name: "tableFilter" }); + + const fetchTables = ApiPostCall({ + queryKey: "CippTables", + onResult: (result) => setTables(result), + }); + + const fetchTableData = ApiPostCall({ + queryKey: "CippTableData", + onResult: (result) => { + setTableData(result); + }, + }); + + const handleTableSelect = (tableName) => { + setSelectedTable(tableName); + fetchTableData.mutate({ + url: apiUrl, + data: { + FunctionName: "Get-AzDataTableEntity", + TableName: tableName, + Parameters: { First: 1000 }, + }, + }); + }; + + const handleRefresh = () => { + if (selectedTable) { + fetchTableData.mutate({ + url: apiUrl, + data: { + FunctionName: "Get-AzDataTableEntity", + TableName: selectedTable, + Parameters: { First: 1000 }, + }, + }); + } + }; + + const tableRowAction = ApiPostCall({ + queryKey: "CippTableRowAction", + onResult: handleRefresh, + }); + + const handleTableRefresh = () => { + fetchTables.mutate({ url: apiUrl, data: { FunctionName: "Get-AzDataTable", Parameters: {} } }); + }; + + useEffect(() => { + handleTableRefresh(); + }, []); + + const actionItems = tables + .filter( + (table) => + tableFilter === "" || + tableFilter === undefined || + table.toLowerCase().includes(tableFilter.toLowerCase()) + ) + .map((table) => ({ + label: `${table}`, + customFunction: () => { + setTableData([]); + handleTableSelect(table); + }, + noConfirm: true, + })); + + const propertyItems = [ + { + label: "", + value: ( + + + + + + + ), + }, + ]; + + const getTableFields = () => { + if (tableData.length === 0) return []; + const sampleRow = tableData[0]; + return Object.keys(sampleRow) + .filter((key) => key !== "ETag" && key !== "Timestamp") + .map((key) => ({ + name: key, + label: key, + type: "textField", + required: false, + })); + }; + + return ( + + + {pageTitle} + + + + a.label.localeCompare(b.label))} + isFetching={fetchTables.isPending} + cardSx={{ maxHeight: "calc(100vh - 170px)", overflow: "auto" }} + actionButton={ + + + + + + + + + + + + + + + + + } + /> + + + {selectedTable && ( + + + + + + } + actions={[ + { + label: "Edit", + type: "POST", + icon: ( + + + + ), + customFunction: (row) => { + setDefaultAddEditValues({ + fields: Object.keys(row) + .filter((key) => key !== "ETag" && key !== "Timestamp") + .map((key) => ({ name: key, value: row[key] })), + }); + addEditRowDialog.handleOpen(); + }, + noConfirm: true, + }, + { + label: "Delete", + type: "POST", + icon: ( + + + + ), + url: apiUrl, + data: { + FunctionName: "Remove-AzDataTableEntity", + TableName: `!${selectedTable}`, + Parameters: { + Entity: { RowKey: "RowKey", PartitionKey: "PartitionKey", ETag: "ETag" }, + }, + }, + onSuccess: handleRefresh, + confirmText: "Do you want to delete this row?", + }, + ]} + /> + + )} + + + { + handleTableRefresh(); + }, + }} + /> + + + + Are you sure you want to delete this table? This is a destructive action that cannot + be undone. + + + ), + type: "POST", + data: { FunctionName: "Remove-AzDataTable", TableName: selectedTable, Parameters: {} }, + onSuccess: () => { + setSelectedTable(null); + setTableData([]); + handleTableRefresh(); + }, + }} + /> + { + const payload = data.fields.reduce((acc, field) => { + acc[field.name] = field.value; + return acc; + }, {}); + tableRowAction.mutate({ + url: apiUrl, + data: { + FunctionName: "Add-AzDataTableEntity", + TableName: selectedTable, + Parameters: { Entity: payload, Force: true }, + }, + onSuccess: handleRefresh, + }); + addEditRowDialog.handleClose(); + }} + defaultValues={defaultAddEditValues} + /> + + ); +}; + +Page.getLayout = (page) => {page}; + +export default Page;