From 55f37089e5cb9a5e8d64de4c7d24857b2d04f18d Mon Sep 17 00:00:00 2001 From: Felipe Trost Date: Wed, 25 Oct 2023 14:15:41 +0200 Subject: [PATCH 01/22] Fixed auth check params --- src/management-system-v2/app/(dashboard)/iam/roles/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/page.tsx b/src/management-system-v2/app/(dashboard)/iam/roles/page.tsx index 83428e206..3f8347fa8 100644 --- a/src/management-system-v2/app/(dashboard)/iam/roles/page.tsx +++ b/src/management-system-v2/app/(dashboard)/iam/roles/page.tsx @@ -146,7 +146,7 @@ const RolesPage: FC = () => { export default Auth( { action: 'manage', - resource: 'User', + resource: 'Role', fallbackRedirect: '/', }, RolesPage, From 43f2936d7d050f0f5cea90e50a70f1d60d6127fe Mon Sep 17 00:00:00 2001 From: Felipe Trost Date: Wed, 25 Oct 2023 14:16:20 +0200 Subject: [PATCH 02/22] Fix response content type on deleteUser --- .../app/(dashboard)/iam/roles/[roleId]/role-members.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx index 832f31963..214fe9891 100644 --- a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx +++ b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx @@ -99,6 +99,7 @@ const RoleMembers: FC<{ role: Role; isLoadingRole?: boolean }> = ({ role, isLoad await Promise.allSettled( userIds.map((userId) => deleteUser({ + parseAs: 'text', params: { path: { roleId: role.id, userId: userId } }, }), ), From 12ee8aaaa5e03440121931cbd07e3b349427c6eb Mon Sep 17 00:00:00 2001 From: Felipe Trost Date: Wed, 25 Oct 2023 14:18:06 +0200 Subject: [PATCH 03/22] Implemented role update functionality --- .../roles/[roleId]/role-permissions-helper.ts | 72 +++++++ .../iam/roles/[roleId]/rolePermissions.tsx | 200 ++++++++++-------- .../components/content.module.scss | 4 +- 3 files changed, 191 insertions(+), 85 deletions(-) create mode 100644 src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-permissions-helper.ts diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-permissions-helper.ts b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-permissions-helper.ts new file mode 100644 index 000000000..1bb299de9 --- /dev/null +++ b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-permissions-helper.ts @@ -0,0 +1,72 @@ +import Ability from '@/lib/ability/abilityHelper'; +import { ResourceActionType } from '@/lib/ability/caslAbility'; +import { ApiData } from '@/lib/fetch-data'; + +type Role = ApiData<'/roles', 'get'>[number]; + +// permission mapping to verbs +const PERMISSION_MAPPING = { + none: 0, + view: 1, + update: 2, + create: 4, + delete: 8, + manage: 16, + share: 32, + 'manage-roles': 64, + 'manage-groups': 128, + 'manage-password': 256, + admin: 9007199254740991, +} as const; + +export function togglePermission( + permissions: Role['permissions'], + resource: keyof Role['permissions'], + permission: keyof typeof PERMISSION_MAPPING, +) { + const currentValue = permissions[resource] ?? 0; + + if (permission !== 'admin') { + const permissionBit = PERMISSION_MAPPING[permission]; + permissions[resource] = currentValue ^ permissionBit; + } else { + permissions[resource] = + currentValue === PERMISSION_MAPPING.admin ? 0 : PERMISSION_MAPPING.admin; + } + + // New pointer for role object to trigger a rerender + return { ...permissions }; +} + +export function switchChecked( + permissions: Role['permissions'] | undefined, + resource: keyof Role['permissions'], + action: ResourceActionType, +) { + if (!(permissions !== undefined && typeof permissions === 'object' && resource in permissions)) + return false; + + const permissionNumber = permissions[resource]!; + + if (action === 'admin') return permissionNumber === PERMISSION_MAPPING.admin; + else if (permissionNumber === PERMISSION_MAPPING.admin) return false; + + return !!(PERMISSION_MAPPING[action] & permissionNumber); +} + +export function switchDisabled( + permissions: Role['permissions'] | undefined, + resource: keyof Role['permissions'], + action: ResourceActionType, + ability: Ability, +) { + if (action === 'admin' && !ability.can('admin', resource)) return true; + + if (!(permissions !== undefined && typeof permissions === 'object' && resource in permissions)) + return false; + + const permissionNumber = permissions[resource]!; + if (permissionNumber === PERMISSION_MAPPING.admin && action !== 'admin') return true; + + return false; +} diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/rolePermissions.tsx b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/rolePermissions.tsx index 6998df9b8..a23cebd1b 100644 --- a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/rolePermissions.tsx +++ b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/rolePermissions.tsx @@ -1,15 +1,28 @@ 'use client'; -import { Divider, Form, Row, Space, Switch, Typography, FloatButton, Tooltip, Spin } from 'antd'; -import { SaveOutlined } from '@ant-design/icons'; -import { ResourceActionType, ResourceType } from '@/lib/ability/caslAbility'; -import { FC } from 'react'; +import { + Divider, + Form, + Row, + Space, + Switch, + Typography, + FloatButton, + Tooltip, + Spin, + App, +} from 'antd'; +import { SaveOutlined, LoadingOutlined } from '@ant-design/icons'; +import { ResourceActionType } from '@/lib/ability/caslAbility'; +import { FC, useState } from 'react'; import { ApiData, usePutAsset } from '@/lib/fetch-data'; +import { useAuthStore } from '@/lib/iam'; +import { switchChecked, switchDisabled, togglePermission } from './role-permissions-helper'; type PermissionCategory = { key: string; title: string; - resource: ResourceType; + resource: keyof Role['permissions']; permissions: { key: string; title: string; @@ -245,91 +258,110 @@ const basePermissionOptions: PermissionCategory[] = [ type Role = ApiData<'/roles', 'get'>[number]; -// permission mapping to verbs -const PERMISSION_MAPPING = { - none: 0, - view: 1, - update: 2, - create: 4, - delete: 8, - manage: 16, - share: 32, - 'manage-roles': 64, - 'manage-groups': 128, - 'manage-password': 256, - admin: 9007199254740991, -}; - -function permissionChecked(role: Role, subject: ResourceType, action: ResourceActionType) { - if ( - !('permissions' in role && typeof role.permissions === 'object' && subject in role.permissions) - ) - return false; - - // @ts-ignore - const permissionNumber = role.permissions[subject]; - - return !!(PERMISSION_MAPPING[action] & permissionNumber); -} - const RolePermissions: FC<{ role: Role }> = ({ role }) => { - const { mutateAsync, isLoading } = usePutAsset('/roles/{id}'); + const [permissions, setPermissions] = useState(role.permissions); + const ability = useAuthStore((store) => store.ability); + const { mutateAsync, isLoading } = usePutAsset('/roles/{id}', { + /* onSuccess: () => message.open({ content: 'Role updated', type: 'success' }), + onError: () => message.open({ content: 'Something went wrong', type: 'error' }), */ + }); + const { message } = App.useApp(); const [form] = Form.useForm(); - function updateRole(values: any) { - // TODO submit role - const newRole = { - description: role.description, - name: role.name, - permissions: role.permissions, - id: role.id, - note: role.note, - default: role.default, - members: role.members, - expiration: role.expiration, - }; + async function updateRole() { + try { + await mutateAsync({ + params: { path: { id: role.id } }, + body: { + description: role.description, + name: role.name, + permissions: permissions, + note: role.note, + default: role.default, + expiration: role.expiration, + }, + }); + + message.open({ content: 'Role updated', type: 'success' }); + } catch (e) { + message.open({ content: 'Something went wrong', type: 'error' }); + } } return ( -
- {basePermissionOptions.map((permissionCategory) => ( - <> - - {permissionCategory.title} - - {permissionCategory.permissions.map((permission, idx) => ( - <> - - - {permission.title} - {permission.description} - - - - - - {idx < permissionCategory.permissions.length - 1 && ( - - )} - - ))} -
- - ))} - - : } - onClick={() => !isLoading && form.submit()} - /> - - +
+
+ {basePermissionOptions.map((permissionCategory) => ( + <> + + {permissionCategory.title} + + {permissionCategory.permissions.map((permission, idx) => ( + <> + + + {permission.title} + {permission.description} + + + + setPermissions( + togglePermission( + permissions, + permissionCategory.resource, + permission.permission, + ), + ) + } + /> + + + {idx < permissionCategory.permissions.length - 1 && ( + + )} + + ))} +
+ + ))} + + + } /> + ) : ( + + ) + } + onClick={() => !isLoading && form.submit()} + /> + + +
); }; diff --git a/src/management-system-v2/components/content.module.scss b/src/management-system-v2/components/content.module.scss index 445748c41..640e54bc4 100644 --- a/src/management-system-v2/components/content.module.scss +++ b/src/management-system-v2/components/content.module.scss @@ -1,5 +1,6 @@ .Main { min-height: 100vh !important; + max-height: 100vh !important; .Header { display: flex; @@ -19,7 +20,8 @@ } .Content { - overflow: initial; + overflow-x: initial; + overflow-y: auto; padding: 40px 20px; max-width: 100%; background-color: white; From 887da729d7b434407497883cc6bcc313f0e3764c Mon Sep 17 00:00:00 2001 From: Felipe Trost Date: Mon, 20 Nov 2023 10:00:01 +0100 Subject: [PATCH 04/22] New ConfirmationButton replaced PopConfirm --- .../iam/roles/[roleId]/role-members.tsx | 31 ++++---- .../app/(dashboard)/iam/roles/role-page.tsx | 24 ++++--- .../app/(dashboard)/iam/users/users-page.tsx | 23 +++--- .../components/confirmation-button.tsx | 72 +++++++++++++++++++ .../components/userProfile.tsx | 20 +++--- 5 files changed, 128 insertions(+), 42 deletions(-) create mode 100644 src/management-system-v2/components/confirmation-button.tsx diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx index 03713544f..021a1c9a6 100644 --- a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx +++ b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx @@ -10,7 +10,8 @@ import { usePostAsset, } from '@/lib/fetch-data'; import UserList, { UserListProps } from '@/components/user-list'; -import { Button, Modal, Popconfirm, Tooltip } from 'antd'; +import { Button, Modal, Tooltip } from 'antd'; +import ConfirmationButton from '@/components/confirmation-button'; type Role = ApiData<'/roles', 'get'>[number]; @@ -119,27 +120,31 @@ const RoleMembers: FC<{ role: Role; isLoadingRole?: boolean }> = ({ role, isLoad title: '', width: 100, render: (id: string) => ( - - + deleteMembers([id], clearSelected)} - > - + + ); +}; + +export default ConfirmationButton; diff --git a/src/management-system-v2/components/userProfile.tsx b/src/management-system-v2/components/userProfile.tsx index fbcda2fc6..c8d8704de 100644 --- a/src/management-system-v2/components/userProfile.tsx +++ b/src/management-system-v2/components/userProfile.tsx @@ -13,13 +13,11 @@ import { Form, Input, Typography, - Popconfirm, Result, Modal, App, } from 'antd'; import styles from './userProfile.module.scss'; -import { useAbilityStore } from '@/lib/abilityStore'; import { ApiData, ApiRequestBody, @@ -29,6 +27,7 @@ import { } from '@/lib/fetch-data'; import { RightOutlined } from '@ant-design/icons'; import { signOut, useSession } from 'next-auth/react'; +import ConfirmationButton from './confirmation-button'; type modalInputField = { userDataField: keyof ApiData<'/users/{id}', 'get'>; @@ -215,7 +214,6 @@ const UserProfile: FC = () => { const [changeNameModalOpen, setChangeNameModalOpen] = useState(false); const [changeEmailModalOpen, setChangeEmailModalOpen] = useState(false); const [changePasswordOpen, setPasswordOpen] = useState(false); - const [deleteUserPopup, setDeleteUserPopup] = useState(false); const { message: messageApi } = App.useApp(); const { mutateAsync: deleteUserMutation } = useDeleteAsset('/users/{id}'); @@ -335,17 +333,17 @@ const UserProfile: FC = () => { - - - + Delete Account + From fe9d7ae269600d74de16c22462bf443baad92ecb Mon Sep 17 00:00:00 2001 From: Felipe Trost Date: Mon, 20 Nov 2023 11:40:40 +0100 Subject: [PATCH 05/22] User and Role table show row actions on hover --- .../iam/roles/[roleId]/role-members.tsx | 14 +++--- .../app/(dashboard)/iam/roles/role-page.tsx | 43 +++++++++++------- .../app/(dashboard)/iam/users/users-page.tsx | 45 +++++++++---------- src/management-system-v2/components/theme.tsx | 1 + .../components/user-list.tsx | 25 +++++++---- 5 files changed, 74 insertions(+), 54 deletions(-) diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx index 021a1c9a6..d4f83a073 100644 --- a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx +++ b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx @@ -61,15 +61,18 @@ const AddUserModal: FC<{ role: Role; open: boolean; close: () => void }> = ({ [ + columns={(clearSelected, hoveredId, selectedRowKeys) => [ { dataIndex: 'id', - render: (_, user) => ( + render: (id, user) => (