From dce6cf6b01716b24179434ce28bb88452282ba38 Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@gmail.com>
Date: Thu, 8 Aug 2024 12:16:36 +1000
Subject: [PATCH] [PUI] Admin tables (#7835)

* Check user permission for tables

* Update permissions for user table

* Fix permission checks for group table

* Permission check for group detail

* Add divider

* Fix permission for template tables
---
 .../AdminCenter/LabelTemplatePanel.tsx        |  2 +
 .../AdminCenter/ReportTemplatePanel.tsx       |  2 +
 .../AdminCenter/UserManagementPanel.tsx       |  2 +
 .../src/tables/settings/GroupTable.tsx        | 47 +++++++-----
 .../src/tables/settings/TemplateTable.tsx     | 10 ++-
 .../src/tables/settings/UserTable.tsx         | 74 ++++++++++++++-----
 6 files changed, 99 insertions(+), 38 deletions(-)

diff --git a/src/frontend/src/pages/Index/Settings/AdminCenter/LabelTemplatePanel.tsx b/src/frontend/src/pages/Index/Settings/AdminCenter/LabelTemplatePanel.tsx
index 2e6e258159..cda9c40efa 100644
--- a/src/frontend/src/pages/Index/Settings/AdminCenter/LabelTemplatePanel.tsx
+++ b/src/frontend/src/pages/Index/Settings/AdminCenter/LabelTemplatePanel.tsx
@@ -1,10 +1,12 @@
 import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
+import { ModelType } from '../../../../enums/ModelType';
 import { TemplateTable } from '../../../../tables/settings/TemplateTable';
 
 export default function LabelTemplatePanel() {
   return (
     <TemplateTable
       templateProps={{
+        modelType: ModelType.labeltemplate,
         templateEndpoint: ApiEndpoints.label_list,
         printingEndpoint: ApiEndpoints.label_print,
         additionalFormFields: {
diff --git a/src/frontend/src/pages/Index/Settings/AdminCenter/ReportTemplatePanel.tsx b/src/frontend/src/pages/Index/Settings/AdminCenter/ReportTemplatePanel.tsx
index 7eab843ddf..1b84993e7d 100644
--- a/src/frontend/src/pages/Index/Settings/AdminCenter/ReportTemplatePanel.tsx
+++ b/src/frontend/src/pages/Index/Settings/AdminCenter/ReportTemplatePanel.tsx
@@ -1,10 +1,12 @@
 import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
+import { ModelType } from '../../../../enums/ModelType';
 import { TemplateTable } from '../../../../tables/settings/TemplateTable';
 
 export default function ReportTemplateTable() {
   return (
     <TemplateTable
       templateProps={{
+        modelType: ModelType.reporttemplate,
         templateEndpoint: ApiEndpoints.report_list,
         printingEndpoint: ApiEndpoints.report_print,
         additionalFormFields: {
diff --git a/src/frontend/src/pages/Index/Settings/AdminCenter/UserManagementPanel.tsx b/src/frontend/src/pages/Index/Settings/AdminCenter/UserManagementPanel.tsx
index 7e38e68bba..3597425f40 100644
--- a/src/frontend/src/pages/Index/Settings/AdminCenter/UserManagementPanel.tsx
+++ b/src/frontend/src/pages/Index/Settings/AdminCenter/UserManagementPanel.tsx
@@ -14,6 +14,8 @@ export default function UserManagementPanel() {
       </Title>
       <UserTable />
 
+      <Divider />
+
       <Title order={5}>
         <Trans>Groups</Trans>
       </Title>
diff --git a/src/frontend/src/tables/settings/GroupTable.tsx b/src/frontend/src/tables/settings/GroupTable.tsx
index 429e26e80f..c9d97637aa 100644
--- a/src/frontend/src/tables/settings/GroupTable.tsx
+++ b/src/frontend/src/tables/settings/GroupTable.tsx
@@ -25,6 +25,7 @@ import {
 import { useInstance } from '../../hooks/UseInstance';
 import { useTable } from '../../hooks/UseTable';
 import { apiUrl } from '../../states/ApiState';
+import { useUserState } from '../../states/UserState';
 import { TableColumn } from '../Column';
 import { InvenTreeTable } from '../InvenTreeTable';
 import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
@@ -127,10 +128,15 @@ export function GroupDrawer({
 export function GroupTable() {
   const table = useTable('groups');
   const navigate = useNavigate();
+  const user = useUserState();
 
   const openDetailDrawer = useCallback(
-    (pk: number) => navigate(`group-${pk}/`),
-    []
+    (pk: number) => {
+      if (user.hasChangePermission(ModelType.group)) {
+        navigate(`group-${pk}/`);
+      }
+    },
+    [user]
   );
 
   const columns: TableColumn<GroupDetailI>[] = useMemo(() => {
@@ -138,24 +144,30 @@ export function GroupTable() {
       {
         accessor: 'name',
         sortable: true,
-        title: t`Name`
+        title: t`Name`,
+        switchable: false
       }
     ];
   }, []);
 
-  const rowActions = useCallback((record: GroupDetailI): RowAction[] => {
-    return [
-      RowEditAction({
-        onClick: () => openDetailDrawer(record.pk)
-      }),
-      RowDeleteAction({
-        onClick: () => {
-          setSelectedGroup(record.pk);
-          deleteGroup.open();
-        }
-      })
-    ];
-  }, []);
+  const rowActions = useCallback(
+    (record: GroupDetailI): RowAction[] => {
+      return [
+        RowEditAction({
+          onClick: () => openDetailDrawer(record.pk),
+          hidden: !user.hasChangePermission(ModelType.group)
+        }),
+        RowDeleteAction({
+          hidden: !user.hasDeletePermission(ModelType.group),
+          onClick: () => {
+            setSelectedGroup(record.pk);
+            deleteGroup.open();
+          }
+        })
+      ];
+    },
+    [user]
+  );
 
   const [selectedGroup, setSelectedGroup] = useState<number>(-1);
 
@@ -183,11 +195,12 @@ export function GroupTable() {
         key={'add-group'}
         onClick={() => newGroup.open()}
         tooltip={t`Add group`}
+        hidden={!user.hasAddPermission(ModelType.group)}
       />
     );
 
     return actions;
-  }, []);
+  }, [user]);
 
   return (
     <>
diff --git a/src/frontend/src/tables/settings/TemplateTable.tsx b/src/frontend/src/tables/settings/TemplateTable.tsx
index 37e25ad526..c6bc59c9f7 100644
--- a/src/frontend/src/tables/settings/TemplateTable.tsx
+++ b/src/frontend/src/tables/settings/TemplateTable.tsx
@@ -48,6 +48,7 @@ export type TemplateI = {
 };
 
 export interface TemplateProps {
+  modelType: ModelType;
   templateEndpoint: ApiEndpoints;
   printingEndpoint: ApiEndpoints;
   additionalFormFields?: ApiFormFieldSet;
@@ -175,18 +176,22 @@ export function TemplateTable({
           title: t`Modify`,
           tooltip: t`Modify template file`,
           icon: <IconFileCode />,
-          onClick: () => openDetailDrawer(record.pk)
+          onClick: () => openDetailDrawer(record.pk),
+          hidden: !user.hasChangePermission(templateProps.modelType)
         },
         RowEditAction({
+          hidden: !user.hasChangePermission(templateProps.modelType),
           onClick: () => {
             setSelectedTemplate(record.pk);
             editTemplate.open();
           }
         }),
         RowDuplicateAction({
+          hidden: true
           // TODO: Duplicate selected template
         }),
         RowDeleteAction({
+          hidden: !user.hasDeletePermission(templateProps.modelType),
           onClick: () => {
             setSelectedTemplate(record.pk);
             deleteTemplate.open();
@@ -252,9 +257,10 @@ export function TemplateTable({
         key="add-template"
         onClick={() => newTemplate.open()}
         tooltip={t`Add template`}
+        hidden={!user.hasAddPermission(templateProps.modelType)}
       />
     ];
-  }, []);
+  }, [user]);
 
   const modelTypeFilters = useFilters({
     url: apiUrl(templateEndpoint),
diff --git a/src/frontend/src/tables/settings/UserTable.tsx b/src/frontend/src/tables/settings/UserTable.tsx
index a7108e64f5..f59ab0047c 100644
--- a/src/frontend/src/tables/settings/UserTable.tsx
+++ b/src/frontend/src/tables/settings/UserTable.tsx
@@ -19,6 +19,8 @@ import {
   DetailDrawerLink
 } from '../../components/nav/DetailDrawer';
 import { ApiEndpoints } from '../../enums/ApiEndpoints';
+import { ModelType } from '../../enums/ModelType';
+import { UserPermissions } from '../../enums/Roles';
 import {
   useCreateApiFormModal,
   useDeleteApiFormModal
@@ -29,6 +31,7 @@ import { apiUrl } from '../../states/ApiState';
 import { useUserState } from '../../states/UserState';
 import { TableColumn } from '../Column';
 import { BooleanColumn } from '../ColumnRenderers';
+import { TableFilter } from '../Filter';
 import { InvenTreeTable } from '../InvenTreeTable';
 import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
 import { GroupDetailI } from './GroupTable';
@@ -163,18 +166,19 @@ export function UserDrawer({
 export function UserTable() {
   const table = useTable('users');
   const navigate = useNavigate();
+  const user = useUserState();
 
   const openDetailDrawer = useCallback(
-    (pk: number) => navigate(`user-${pk}/`),
-    []
+    (pk: number) => {
+      if (user.hasChangePermission(ModelType.user)) {
+        navigate(`user-${pk}/`);
+      }
+    },
+    [user]
   );
 
   const columns: TableColumn[] = useMemo(() => {
     return [
-      {
-        accessor: 'email',
-        sortable: true
-      },
       {
         accessor: 'username',
         sortable: true,
@@ -188,8 +192,13 @@ export function UserTable() {
         accessor: 'last_name',
         sortable: true
       },
+      {
+        accessor: 'email',
+        sortable: true
+      },
       {
         accessor: 'groups',
+        title: t`Groups`,
         sortable: true,
         switchable: true,
         render: (record: any) => {
@@ -211,19 +220,24 @@ export function UserTable() {
   // Row Actions
   const [selectedUser, setSelectedUser] = useState<number>(-1);
 
-  const rowActions = useCallback((record: UserDetailI): RowAction[] => {
-    return [
-      RowEditAction({
-        onClick: () => openDetailDrawer(record.pk)
-      }),
-      RowDeleteAction({
-        onClick: () => {
-          setSelectedUser(record.pk);
-          deleteUser.open();
-        }
-      })
-    ];
-  }, []);
+  const rowActions = useCallback(
+    (record: UserDetailI): RowAction[] => {
+      return [
+        RowEditAction({
+          onClick: () => openDetailDrawer(record.pk),
+          hidden: !user.hasChangePermission(ModelType.user)
+        }),
+        RowDeleteAction({
+          hidden: !user.hasDeletePermission(ModelType.user),
+          onClick: () => {
+            setSelectedUser(record.pk);
+            deleteUser.open();
+          }
+        })
+      ];
+    },
+    [user]
+  );
 
   const deleteUser = useDeleteApiFormModal({
     url: ApiEndpoints.user_list,
@@ -256,10 +270,31 @@ export function UserTable() {
         key="add-user"
         onClick={newUser.open}
         tooltip={t`Add user`}
+        hidden={!user.hasAddPermission(ModelType.user)}
       />
     );
 
     return actions;
+  }, [user]);
+
+  const tableFilters: TableFilter[] = useMemo(() => {
+    return [
+      {
+        name: 'is_active',
+        label: t`Active`,
+        description: t`Show active users`
+      },
+      {
+        name: 'is_staff',
+        label: t`Staff`,
+        description: t`Show staff users`
+      },
+      {
+        name: 'is_superuser',
+        label: t`Superuser`,
+        description: t`Show superusers`
+      }
+    ];
   }, []);
 
   return (
@@ -285,6 +320,7 @@ export function UserTable() {
         props={{
           rowActions: rowActions,
           tableActions: tableActions,
+          tableFilters: tableFilters,
           onRowClick: (record) => openDetailDrawer(record.pk)
         }}
       />