From ce26a06129879a7af0c7629b02103f89479a61a7 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Thu, 18 Jul 2024 17:12:02 +0700 Subject: [PATCH 01/10] role table restyle --- .../IdentitySection/IdentitySection.tsx | 2 +- .../OrgRoleTabSection/OrgRoleTable.tsx | 130 ++++++++++-------- 2 files changed, 71 insertions(+), 61 deletions(-) diff --git a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx index f08196df6..d7134dfee 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx @@ -68,7 +68,7 @@ export const IdentitySection = withPermission( }; return ( -
+

Identities

diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx index b4e08f51a..8046dd948 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx @@ -1,14 +1,16 @@ -import { useState } from "react"; -import { faEdit, faMagnifyingGlass, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faEllipsis,faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal, - IconButton, - Input, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, Table, TableContainer, TableSkeleton, @@ -28,11 +30,12 @@ type Props = { }; export const OrgRoleTable = ({ onSelectRole }: Props) => { - const [searchRoles, setSearchRoles] = useState(""); const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; - const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["deleteRole"] as const); + const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ + "deleteRole" + ] as const); const { data: roles, isLoading: isRolesLoading } = useGetOrgRoles(orgId); @@ -54,50 +57,52 @@ export const OrgRoleTable = ({ onSelectRole }: Props) => { }; return ( -
-
-
- setSearchRoles(e.target.value)} - leftIcon={} - placeholder="Search roles..." - /> -
+
+
+

Organization Roles

{(isAllowed) => ( )}
-
- - - - - - - - - - {isRolesLoading && } - {roles?.map((role) => { - const { id, name, slug } = role; - const isNonMutatable = ["owner", "admin", "member", "no-access"].includes(slug); - - return ( - - - - + + ); + })} + +
NameSlug -
{name}{slug} -
+ + + + + + + + + + {isRolesLoading && } + {roles?.map((role) => { + const { id, name, slug } = role; + const isNonMutatable = ["owner", "admin", "member", "no-access"].includes(slug); + return ( + + + + - - ); - })} - -
NameSlug +
{name}{slug} + + +
+ +
+
+ { allowedLabel="Edit" > {(isAllowed) => ( - onSelectRole(role)} - variant="plain" + disabled={!isAllowed} > - - + Edit Role + )} { } > {(isAllowed) => ( - handlePopUpOpen("deleteRole", role)} - variant="plain" - isDisabled={isNonMutatable || !isAllowed} + disabled={!isAllowed} > - - + Delete Role + )} - -
-
-
+ + +
+
handlePopUpToggle("deleteRole", isOpen)} deleteKey={(popUp?.deleteRole?.data as TOrgRole)?.slug || ""} onClose={() => handlePopUpClose("deleteRole")} onDeleteApproved={handleRoleDelete} From 7127f6d1e11924469da196237e40d0a4b74c53ae Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Thu, 18 Jul 2024 20:12:52 +0700 Subject: [PATCH 02/10] Begin role page --- backend/src/ee/routes/v1/org-role-router.ts | 31 ++ backend/src/services/org/org-role-service.ts | 16 +- frontend/src/hooks/api/roles/index.tsx | 1 + frontend/src/hooks/api/roles/queries.tsx | 13 + .../pages/org/[id]/roles/[roleId]/index.tsx | 20 ++ .../OrgRoleTabSection/OrgRoleTable.tsx | 10 +- frontend/src/views/Org/RolePage/RolePage.tsx | 285 ++++++++++++++++++ .../components/RoleDetailsSection.tsx | 87 ++++++ .../views/Org/RolePage/components/index.tsx | 1 + frontend/src/views/Org/RolePage/index.tsx | 1 + 10 files changed, 462 insertions(+), 3 deletions(-) create mode 100644 frontend/src/pages/org/[id]/roles/[roleId]/index.tsx create mode 100644 frontend/src/views/Org/RolePage/RolePage.tsx create mode 100644 frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx create mode 100644 frontend/src/views/Org/RolePage/components/index.tsx create mode 100644 frontend/src/views/Org/RolePage/index.tsx diff --git a/backend/src/ee/routes/v1/org-role-router.ts b/backend/src/ee/routes/v1/org-role-router.ts index 6691032a8..f96b89609 100644 --- a/backend/src/ee/routes/v1/org-role-router.ts +++ b/backend/src/ee/routes/v1/org-role-router.ts @@ -52,6 +52,37 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + // new: note that doesn't work for default roles + method: "GET", + url: "/:organizationId/roles/:roleId", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + organizationId: z.string().trim(), + roleId: z.string().trim() + }), + response: { + 200: z.object({ + role: OrgRolesSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const role = await server.services.orgRole.getRole( + req.permission.id, + req.params.organizationId, + req.params.roleId, + req.permission.authMethod, + req.permission.orgId + ); + return { role }; + } + }); + server.route({ method: "PATCH", url: "/:organizationId/roles/:roleId", diff --git a/backend/src/services/org/org-role-service.ts b/backend/src/services/org/org-role-service.ts index 70c54ff18..262f98bc6 100644 --- a/backend/src/services/org/org-role-service.ts +++ b/backend/src/services/org/org-role-service.ts @@ -42,6 +42,20 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return role; }; + const getRole = async ( + userId: string, + orgId: string, + roleId: string, + actorAuthMethod: ActorAuthMethod, + actorOrgId: string | undefined + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role); + const role = await orgRoleDAL.findOne({ id: roleId, orgId }); + if (!role) throw new BadRequestError({ message: "Role not found", name: "Get role" }); + return role; + }; + const updateRole = async ( userId: string, orgId: string, @@ -144,5 +158,5 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return { permissions: packRules(permission.rules), membership }; }; - return { createRole, updateRole, deleteRole, listRoles, getUserPermission }; + return { createRole, getRole, updateRole, deleteRole, listRoles, getUserPermission }; }; diff --git a/frontend/src/hooks/api/roles/index.tsx b/frontend/src/hooks/api/roles/index.tsx index 53c05a7b6..fd8e4e705 100644 --- a/frontend/src/hooks/api/roles/index.tsx +++ b/frontend/src/hooks/api/roles/index.tsx @@ -7,6 +7,7 @@ export { useUpdateProjectRole } from "./mutation"; export { + useGetOrgRole, useGetOrgRoles, useGetProjectRoleBySlug, useGetProjectRoles, diff --git a/frontend/src/hooks/api/roles/queries.tsx b/frontend/src/hooks/api/roles/queries.tsx index f04af697d..47685b749 100644 --- a/frontend/src/hooks/api/roles/queries.tsx +++ b/frontend/src/hooks/api/roles/queries.tsx @@ -40,6 +40,7 @@ export const roleQueryKeys = { getProjectRoleBySlug: (projectSlug: string, roleSlug: string) => ["roles", { projectSlug, roleSlug }] as const, getOrgRoles: (orgId: string) => ["org-roles", { orgId }] as const, + getOrgRole: (orgId: string, roleId: string) => [{ orgId, roleId }, "org-role"] as const, getUserOrgPermissions: ({ orgId }: TGetUserOrgPermissionsDTO) => ["user-permissions", { orgId }] as const, getUserProjectPermissions: ({ workspaceId }: TGetUserProjectPermissionDTO) => @@ -89,6 +90,18 @@ export const useGetOrgRoles = (orgId: string, enable = true) => enabled: Boolean(orgId) && enable }); +export const useGetOrgRole = (orgId: string, roleId: string) => + useQuery({ + queryKey: roleQueryKeys.getOrgRole(orgId, roleId), + queryFn: async () => { + const { data } = await apiRequest.get<{ role: TProjectRole }>( + `/api/v1/organization/${orgId}/roles/${roleId}` // TODO: implement + ); + return data.role; + }, + enabled: Boolean(orgId && roleId) + }); + const getUserOrgPermissions = async ({ orgId }: TGetUserOrgPermissionsDTO) => { if (orgId === "") return { permissions: [], membership: null }; diff --git a/frontend/src/pages/org/[id]/roles/[roleId]/index.tsx b/frontend/src/pages/org/[id]/roles/[roleId]/index.tsx new file mode 100644 index 000000000..082f2d885 --- /dev/null +++ b/frontend/src/pages/org/[id]/roles/[roleId]/index.tsx @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { useTranslation } from "react-i18next"; +import Head from "next/head"; + +import { RolePage } from "@app/views/Org/RolePage"; + +export default function Role() { + const { t } = useTranslation(); + return ( + <> + + {t("common.head-title", { title: t("settings.org.title") })} + + + + + ); +} + +Role.requireAuth = true; diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx index 8046dd948..dab3e8e74 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx @@ -1,4 +1,5 @@ -import { faEllipsis,faPlus } from "@fortawesome/free-solid-svg-icons"; +import { useRouter } from "next/router"; +import { faEllipsis, faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; @@ -30,6 +31,7 @@ type Props = { }; export const OrgRoleTable = ({ onSelectRole }: Props) => { + const router = useRouter(); const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; @@ -92,6 +94,7 @@ export const OrgRoleTable = ({ onSelectRole }: Props) => { router.push(`/org/${orgId}/roles/${id}`)} > {name} {slug} @@ -114,7 +117,10 @@ export const OrgRoleTable = ({ onSelectRole }: Props) => { className={twMerge( !isAllowed && "pointer-events-none cursor-not-allowed opacity-50" )} - onClick={() => onSelectRole(role)} + onClick={(e) => { + e.stopPropagation(); + onSelectRole(role); + }} disabled={!isAllowed} > Edit Role diff --git a/frontend/src/views/Org/RolePage/RolePage.tsx b/frontend/src/views/Org/RolePage/RolePage.tsx new file mode 100644 index 000000000..d290b45b4 --- /dev/null +++ b/frontend/src/views/Org/RolePage/RolePage.tsx @@ -0,0 +1,285 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { useRouter } from "next/router"; +import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; + +import { createNotification } from "@app/components/notifications"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { + Button, + DeleteActionModal, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + Tooltip, + UpgradePlanModal +} from "@app/components/v2"; +import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; +import { withPermission } from "@app/hoc"; +import { + // useDeleteIdentity, + // useGetIdentityById, + // useRevokeIdentityTokenAuthToken, + // useRevokeIdentityUniversalAuthClientSecret, + useGetOrgRole +} from "@app/hooks/api"; +import { usePopUp } from "@app/hooks/usePopUp"; + +import { RoleDetailsSection } from "./components"; + +// import { IdentityAuthMethodModal } from "../MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal"; +// import { IdentityModal } from "../MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal"; +// import { IdentityUniversalAuthClientSecretModal } from "../MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal"; +// import { +// IdentityAuthenticationSection, +// IdentityClientSecretModal, +// IdentityDetailsSection, +// IdentityProjectsSection, +// IdentityTokenListModal, +// IdentityTokenModal +// } from "./components"; + +export const RolePage = withPermission( + () => { + const router = useRouter(); + const roleId = router.query.roleId as string; + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + const { data } = useGetOrgRole(orgId, roleId); + console.log("useGetOrgRole data: ", data); + + // const { data } = useGetIdentityById(identityId); // TODO: get role by id + // const { mutateAsync: deleteIdentity } = useDeleteIdentity(); + // const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken(); + // const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret(); + + const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ + "identity", + "deleteIdentity", + "identityAuthMethod", + "revokeAuthMethod", + "token", + "tokenList", + "revokeToken", + "clientSecret", + "revokeClientSecret", + "universalAuthClientSecret", // list of client secrets + "upgradePlan" + ] as const); + + // const onDeleteIdentitySubmit = async (id: string) => { + // try { + // await deleteIdentity({ + // identityId: id, + // organizationId: orgId + // }); + + // createNotification({ + // text: "Successfully deleted identity", + // type: "success" + // }); + + // handlePopUpClose("deleteIdentity"); + // router.push(`/org/${orgId}/members`); + // } catch (err) { + // console.error(err); + // const error = err as any; + // const text = error?.response?.data?.message ?? "Failed to delete identity"; + + // createNotification({ + // text, + // type: "error" + // }); + // } + // }; + + // const onRevokeTokenSubmit = async ({ + // identityId: parentIdentityId, + // tokenId, + // name + // }: { + // identityId: string; + // tokenId: string; + // name: string; + // }) => { + // try { + // await revokeToken({ + // identityId: parentIdentityId, + // tokenId + // }); + + // handlePopUpClose("revokeToken"); + + // createNotification({ + // text: `Successfully revoked token ${name ?? ""}`, + // type: "success" + // }); + // } catch (err) { + // console.error(err); + // const error = err as any; + // const text = error?.response?.data?.message ?? "Failed to delete identity"; + + // createNotification({ + // text, + // type: "error" + // }); + // } + // }; + + return ( +
+ {data && ( +
+ +
+

{data.name}

+ + +
+ + + +
+
+ + + {(isAllowed) => ( + { + // handlePopUpOpen("identity", { + // identityId, + // name: data.identity.name, + // role: data.role, + // customRole: data.customRole + // }); + }} + disabled={!isAllowed} + > + Edit Role + + )} + + + {(isAllowed) => ( + { + // handlePopUpOpen("deleteIdentity", { + // identityId, + // name: data.identity.name + // }); + }} + disabled={!isAllowed} + > + Delete Role + + )} + + +
+
+
+
+ +
+ Permissions Section + {/* */} +
+
+ )} + {/* + + + + + + handlePopUpToggle("upgradePlan", isOpen)} + text={(popUp.upgradePlan?.data as { description: string })?.description} + /> + handlePopUpToggle("deleteIdentity", isOpen)} + deleteKey="confirm" + onDeleteApproved={() => + onDeleteIdentitySubmit( + (popUp?.deleteIdentity?.data as { identityId: string })?.identityId + ) + } + /> + handlePopUpToggle("revokeToken", isOpen)} + deleteKey="confirm" + onDeleteApproved={() => { + const revokeTokenData = popUp?.revokeToken?.data as { + identityId: string; + tokenId: string; + name: string; + }; + + return onRevokeTokenSubmit(revokeTokenData); + }} + /> + handlePopUpToggle("revokeClientSecret", isOpen)} + deleteKey="confirm" + onDeleteApproved={() => { + const deleteClientSecretData = popUp?.revokeClientSecret?.data as { + clientSecretId: string; + clientSecretPrefix: string; + }; + + return onDeleteClientSecretSubmit({ + clientSecretId: deleteClientSecretData.clientSecretId + }); + }} + /> */} +
+ ); + }, + { action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Role } +); diff --git a/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx b/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx new file mode 100644 index 000000000..42a3a0e67 --- /dev/null +++ b/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx @@ -0,0 +1,87 @@ +import { faCheck, faCopy, faPencil } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { OrgPermissionCan } from "@app/components/permissions"; +import { IconButton, Tooltip } from "@app/components/v2"; +import { OrgPermissionActions, OrgPermissionSubjects,useOrganization } from "@app/context"; +import { useTimedReset } from "@app/hooks"; +import { useGetOrgRole } from "@app/hooks/api"; +// import { UsePopUpState } from "@app/hooks/usePopUp"; + +type Props = { + roleId: string; + // handlePopUpOpen: (popUpName: keyof UsePopUpState<[]>, data?: {}) => void; +}; + +export const RoleDetailsSection = ({ roleId }: Props) => { + const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset({ + initialState: "Copy ID to clipboard" + }); + + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + const { data } = useGetOrgRole(orgId, roleId); + + console.log("useGetOrgRole data: ", data); + + return data ? ( +
+
+

Details

+ + {(isAllowed) => { + return ( + + { + // handlePopUpOpen("identity", { + // identityId, + // name: data.identity.name, + // role: data.role, + // customRole: data.customRole + // }); + }} + > + + + + ); + }} + +
+
+
+

Role ID

+
+

{roleId}

+
+ + { + navigator.clipboard.writeText(roleId); + setCopyTextId("Copied"); + }} + > + + + +
+
+
+
+

Name

+

{data.name}

+
+
+
+ ) : ( +
+ ); +}; diff --git a/frontend/src/views/Org/RolePage/components/index.tsx b/frontend/src/views/Org/RolePage/components/index.tsx new file mode 100644 index 000000000..0432a2cba --- /dev/null +++ b/frontend/src/views/Org/RolePage/components/index.tsx @@ -0,0 +1 @@ +export { RoleDetailsSection } from "./RoleDetailsSection"; diff --git a/frontend/src/views/Org/RolePage/index.tsx b/frontend/src/views/Org/RolePage/index.tsx new file mode 100644 index 000000000..71e7114fc --- /dev/null +++ b/frontend/src/views/Org/RolePage/index.tsx @@ -0,0 +1 @@ +export { RolePage } from "./RolePage"; From 22878a035be2132ec14f63b1f82a4be439869d65 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 19 Jul 2024 15:24:21 +0700 Subject: [PATCH 03/10] Continue RolePermissionsTable --- .../src/ee/services/license/licence-fns.ts | 2 +- .../RolePermissionsSection.tsx | 93 +++++++++++++++++++ .../RolePermissionsTable.tsx | 62 +++++++++++++ .../RolePermissionsSection/index.tsx | 1 + 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx create mode 100644 frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx create mode 100644 frontend/src/views/Org/RolePage/components/RolePermissionsSection/index.tsx diff --git a/backend/src/ee/services/license/licence-fns.ts b/backend/src/ee/services/license/licence-fns.ts index 3e30276cb..8da8afd39 100644 --- a/backend/src/ee/services/license/licence-fns.ts +++ b/backend/src/ee/services/license/licence-fns.ts @@ -32,7 +32,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ oidcSSO: false, scim: false, ldap: false, - groups: false, + groups: true, status: null, trial_end: null, has_used_trial: true, diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx new file mode 100644 index 000000000..4346bce3b --- /dev/null +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -0,0 +1,93 @@ +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +// import { createNotification } from "@app/components/notifications"; +import { + // DeleteActionModal, + IconButton +} from "@app/components/v2"; +// import { useDeleteIdentityFromWorkspace } from "@app/hooks/api"; +// import { usePopUp } from "@app/hooks/usePopUp"; + +// import { IdentityAddToProjectModal } from "./IdentityAddToProjectModal"; +// import { IdentityProjectsTable } from "./IdentityProjectsTable"; + +export const RolePermissionsSesction = () => { + // const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace(); + + // const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ + // "addIdentityToProject", + // "removeIdentityFromProject" + // ] as const); + + // const onRemoveIdentitySubmit = async (id: string, projectId: string) => { + // try { + // await deleteMutateAsync({ + // identityId: id, + // workspaceId: projectId + // }); + + // createNotification({ + // text: "Successfully removed identity from project", + // type: "success" + // }); + + // handlePopUpClose("removeIdentityFromProject"); + // } catch (err) { + // console.error(err); + // const error = err as any; + // const text = error?.response?.data?.message ?? "Failed to remove identity from project"; + + // createNotification({ + // text, + // type: "error" + // }); + // } + // }; + + return ( +
+
+

Permissions

+ { + console.log("TODO"); + // handlePopUpOpen("addIdentityToProject"); + }} + > + + +
+
+ Permissions Table + {/* */} +
+ {/* handlePopUpToggle("removeIdentityFromProject", isOpen)} + deleteKey="confirm" + onDeleteApproved={() => { + const popupData = popUp?.removeIdentityFromProject?.data as { + identityId: string; + projectId: string; + }; + + return onRemoveIdentitySubmit(popupData.identityId, popupData.projectId); + }} + /> */} + {/* */} +
+ ); +}; diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx new file mode 100644 index 000000000..8cf3b857d --- /dev/null +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx @@ -0,0 +1,62 @@ +import { faKey } from "@fortawesome/free-solid-svg-icons"; + +import { + EmptyState, + Table, + TableContainer, + TableSkeleton, + TBody, + Th, + THead, + Tr +} from "@app/components/v2"; +import { useGetIdentityProjectMemberships } from "@app/hooks/api"; +// import { UsePopUpState } from "@app/hooks/usePopUp"; + +// import { IdentityProjectRow } from "./IdentityProjectRow"; + +type Props = { + identityId: string; + // handlePopUpOpen: ( + // popUpName: keyof UsePopUpState<["removeIdentityFromProject"]>, + // data?: {} + // ) => void; +}; + +export const IdentityProjectsTable = ({ + identityId +}: // handlePopUpOpen +Props) => { + const { data: projectMemberships, isLoading } = useGetIdentityProjectMemberships(identityId); + return ( + + + + + + + + + + + {isLoading && } + {!isLoading && + projectMemberships?.map((membership) => { + return ( +
Row
+ // + ); + })} +
+
NameRoleAdded On +
+ {!isLoading && !projectMemberships?.length && ( + + )} +
+ ); +}; diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/index.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/index.tsx new file mode 100644 index 000000000..9a451ebef --- /dev/null +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/index.tsx @@ -0,0 +1 @@ +export { RolePermissionsSesction } from "./RolePermissionsSection"; From 3731459e991901769de02b04bb1ae3307e9008b9 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 19 Jul 2024 16:53:04 +0700 Subject: [PATCH 04/10] Make progress on org role detail modal --- frontend/src/views/Org/RolePage/RolePage.tsx | 6 +- .../RolePage/components/RoleDetailsModal.tsx | 218 ++++++++++++++++++ .../RolePermissionsSection.tsx | 6 +- .../RolePermissionsTable.tsx | 124 +++++++--- .../RolePermissionsSection/index.tsx | 2 +- .../views/Org/RolePage/components/index.tsx | 1 + 6 files changed, 320 insertions(+), 37 deletions(-) create mode 100644 frontend/src/views/Org/RolePage/components/RoleDetailsModal.tsx diff --git a/frontend/src/views/Org/RolePage/RolePage.tsx b/frontend/src/views/Org/RolePage/RolePage.tsx index d290b45b4..d939a28b9 100644 --- a/frontend/src/views/Org/RolePage/RolePage.tsx +++ b/frontend/src/views/Org/RolePage/RolePage.tsx @@ -27,7 +27,8 @@ import { } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; -import { RoleDetailsSection } from "./components"; +import { RolePermissionsTable } from "./components/RolePermissionsSection/RolePermissionsTable"; +import { RoleDetailsSection, RolePermissionsSection } from "./components"; // import { IdentityAuthMethodModal } from "../MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal"; // import { IdentityModal } from "../MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal"; @@ -201,8 +202,7 @@ export const RolePage = withPermission(
- Permissions Section - {/* */} +
)} diff --git a/frontend/src/views/Org/RolePage/components/RoleDetailsModal.tsx b/frontend/src/views/Org/RolePage/components/RoleDetailsModal.tsx new file mode 100644 index 000000000..68f6f2b75 --- /dev/null +++ b/frontend/src/views/Org/RolePage/components/RoleDetailsModal.tsx @@ -0,0 +1,218 @@ +// import { useEffect } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +// import { useRouter } from "next/router"; +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { + useGetOrgRole + // useCreateOrgRole, + // useUpdateOrgRole +} from "@app/hooks/api"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +const schema = z + .object({ + name: z.string(), + description: z.string(), + slug: z.string() + }) + .required(); + +export type FormData = z.infer; + +type Props = { + popUp: UsePopUpState<["role"]>; + // handlePopUpOpen: ( + // popUpName: keyof UsePopUpState<["identityAuthMethod"]>, + // data: { + // identityId: string; + // name: string; + // authMethod?: IdentityAuthMethod; + // } + // ) => void; + handlePopUpToggle: (popUpName: keyof UsePopUpState<["role"]>, state?: boolean) => void; +}; + +export const RoleDetailsModal = ({ popUp, handlePopUpToggle }: Props) => { + // const router = useRouter(); + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + + const role = popUp?.role?.data as { + roleId: string; + }; + + const { data } = useGetOrgRole(orgId, role.roleId ?? ""); + console.log("RoleDetailsModal: useGetOrgRole data: ", data); + + // TODO: fetch by role id here + + // const { mutateAsync: createMutateAsync } = useCreateIdentity(); + // const { mutateAsync: updateMutateAsync } = useUpdateIdentity(); + // const { mutateAsync: addMutateAsync } = useAddIdentityUniversalAuth(); + + // const { mutateAsync: createOrgRole } = useCreateOrgRole(); + // const { mutateAsync: updateOrgRole } = useUpdateOrgRole(); + + const { + control, + handleSubmit, + reset, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + name: "", + description: "" + } + }); + + // useEffect(() => { + // if (role) { + // reset({ + // name: role.name, + // description: role.description, + // slug: role.slug + // }); + // } else { + // reset({ + // name: "", + // description: "", + // slug: "" + // }); + // } + // }, [role]); + + const onFormSubmit = async ({ name, description, slug }: FormData) => { + try { + console.log("onFormSubmit args: ", { + name, + description, + slug + }); + + if (role) { + // update + + // const up = await updateOrgRole({}); + + // console.log("onFormSubmit up: ", up); + + // await updateMutateAsync({ + // identityId: identity.identityId, + // name, + // role: role || undefined, + // organizationId: orgId + // }); + + handlePopUpToggle("role", false); + } else { + // create + + // const { id: createdId } = await createMutateAsync({ + // name, + // role: role || undefined, + // organizationId: orgId + // }); + + // await addMutateAsync({ + // organizationId: orgId, + // identityId: createdId, + // clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], + // accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], + // accessTokenTTL: 2592000, + // accessTokenMaxTTL: 2592000, + // accessTokenNumUsesLimit: 0 + // }); + + handlePopUpToggle("role", false); + // router.push(`/org/${orgId}/identities/${createdId}`); + } + + createNotification({ + text: `Successfully ${popUp?.role?.data ? "updated" : "created"} role`, + type: "success" + }); + + reset(); + } catch (err) { + console.error(err); + const error = err as any; + const text = + error?.response?.data?.message ?? + `Failed to ${popUp?.role?.data ? "update" : "create"} role`; + + createNotification({ + text, + type: "error" + }); + } + }; + + return ( + { + handlePopUpToggle("role", isOpen); + reset(); + }} + > + +
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> +
+ + +
+ +
+
+ ); +}; diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx index 4346bce3b..347fb6ba2 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -6,13 +6,15 @@ import { // DeleteActionModal, IconButton } from "@app/components/v2"; + +import { RolePermissionsTable } from "./RolePermissionsTable"; // import { useDeleteIdentityFromWorkspace } from "@app/hooks/api"; // import { usePopUp } from "@app/hooks/usePopUp"; // import { IdentityAddToProjectModal } from "./IdentityAddToProjectModal"; // import { IdentityProjectsTable } from "./IdentityProjectsTable"; -export const RolePermissionsSesction = () => { +export const RolePermissionsSection = () => { // const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace(); // const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ @@ -62,7 +64,7 @@ export const RolePermissionsSesction = () => {
- Permissions Table + {/* */}
{/* , - // data?: {} - // ) => void; -}; +// type Props = { +// identityId: string; +// handlePopUpOpen: ( +// popUpName: keyof UsePopUpState<["removeIdentityFromProject"]>, +// data?: {} +// ) => void; +// }; -export const IdentityProjectsTable = ({ - identityId -}: // handlePopUpOpen -Props) => { - const { data: projectMemberships, isLoading } = useGetIdentityProjectMemberships(identityId); +export const RolePermissionsTable = () => { + // const { data: projectMemberships, isLoading } = useGetIdentityProjectMemberships(identityId); return ( - - - + + - {isLoading && } - {!isLoading && + + + + + + {/* */} + {/* {isLoading && } */} + {/* {!isLoading && projectMemberships?.map((membership) => { return (
Row
- // + ); - })} + })} */}
NameRoleAdded OnResourceAllowed Actions
IdentityCreate/Read + + +
+ +
+
+ + + {(isAllowed) => ( + { + e.stopPropagation(); + // TODO + }} + disabled={!isAllowed} + > + Edit Permission + + )} + + + {(isAllowed) => ( + { + e.stopPropagation(); + // handlePopUpOpen("deleteIdentity", { + // identityId: id, + // name + // }); + }} + disabled={!isAllowed} + > + Delete Permission + + )} + + +
+
- {!isLoading && !projectMemberships?.length && ( + {/* */} + {/* {!isLoading && !projectMemberships?.length && ( - )} + )} */}
); }; diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/index.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/index.tsx index 9a451ebef..104e2144e 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/index.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/index.tsx @@ -1 +1 @@ -export { RolePermissionsSesction } from "./RolePermissionsSection"; +export { RolePermissionsSection } from "./RolePermissionsSection"; diff --git a/frontend/src/views/Org/RolePage/components/index.tsx b/frontend/src/views/Org/RolePage/components/index.tsx index 0432a2cba..dadfb93af 100644 --- a/frontend/src/views/Org/RolePage/components/index.tsx +++ b/frontend/src/views/Org/RolePage/components/index.tsx @@ -1 +1,2 @@ export { RoleDetailsSection } from "./RoleDetailsSection"; +export { RolePermissionsSection } from "./RolePermissionsSection"; From bd860e6c5ac5a3d3f35124241e6fde6d434ea6cb Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 22 Jul 2024 12:41:26 +0700 Subject: [PATCH 05/10] Continue progress on role ui update --- backend/src/ee/routes/v1/org-role-router.ts | 2 +- frontend/src/hooks/api/roles/mutation.tsx | 20 +- frontend/src/hooks/api/roles/queries.tsx | 11 +- .../OrgRoleModifySection.tsx | 2 - frontend/src/views/Org/RolePage/RolePage.tsx | 43 +-- .../components/RoleDetailsSection.tsx | 31 +- .../{RoleDetailsModal.tsx => RoleModal.tsx} | 95 +++-- .../components/RolePermissionModal.tsx | 350 ++++++++++++++++++ .../RolePermissionRow2.tsx | 215 +++++++++++ .../RolePermissionsSection.tsx | 37 +- .../RolePermissionsTable2.tsx | 103 ++++++ .../views/Org/RolePage/components/index.tsx | 2 + .../SecretOverviewTableRow.tsx | 1 - 13 files changed, 779 insertions(+), 133 deletions(-) rename frontend/src/views/Org/RolePage/components/{RoleDetailsModal.tsx => RoleModal.tsx} (71%) create mode 100644 frontend/src/views/Org/RolePage/components/RolePermissionModal.tsx create mode 100644 frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow2.tsx create mode 100644 frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable2.tsx diff --git a/backend/src/ee/routes/v1/org-role-router.ts b/backend/src/ee/routes/v1/org-role-router.ts index f96b89609..559bd1611 100644 --- a/backend/src/ee/routes/v1/org-role-router.ts +++ b/backend/src/ee/routes/v1/org-role-router.ts @@ -100,7 +100,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { .trim() .optional() .refine( - (val) => typeof val === "undefined" || Object.keys(OrgMembershipRole).includes(val), + (val) => typeof val !== "undefined" && !Object.keys(OrgMembershipRole).includes(val), "Please choose a different slug, the slug you have entered is reserved." ) .refine((val) => typeof val === "undefined" || slugify(val) === val, { diff --git a/frontend/src/hooks/api/roles/mutation.tsx b/frontend/src/hooks/api/roles/mutation.tsx index ae3e170de..db94c7403 100644 --- a/frontend/src/hooks/api/roles/mutation.tsx +++ b/frontend/src/hooks/api/roles/mutation.tsx @@ -68,13 +68,22 @@ export const useUpdateOrgRole = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: ({ id, orgId, permissions, ...dto }: TUpdateOrgRoleDTO) => - apiRequest.patch(`/api/v1/organization/${orgId}/roles/${id}`, { + mutationFn: ({ id, orgId, permissions, ...dto }: TUpdateOrgRoleDTO) => { + console.log("update args: ", { + id, + orgId, + permissions, + ...dto, + pack: permissions?.length ? packRules(permissions) : [] + }); + return apiRequest.patch(`/api/v1/organization/${orgId}/roles/${id}`, { ...dto, permissions: permissions?.length ? packRules(permissions) : [] - }), - onSuccess: (_, { orgId }) => { + }); + }, + onSuccess: (_, { id, orgId }) => { queryClient.invalidateQueries(roleQueryKeys.getOrgRoles(orgId)); + queryClient.invalidateQueries(roleQueryKeys.getOrgRole(orgId, id)); } }); }; @@ -87,8 +96,9 @@ export const useDeleteOrgRole = () => { apiRequest.delete(`/api/v1/organization/${orgId}/roles/${id}`, { data: { orgId } }), - onSuccess: (_, { orgId }) => { + onSuccess: (_, { id, orgId }) => { queryClient.invalidateQueries(roleQueryKeys.getOrgRoles(orgId)); + queryClient.invalidateQueries(roleQueryKeys.getOrgRole(orgId, id)); } }); }; diff --git a/frontend/src/hooks/api/roles/queries.tsx b/frontend/src/hooks/api/roles/queries.tsx index 47685b749..865d28789 100644 --- a/frontend/src/hooks/api/roles/queries.tsx +++ b/frontend/src/hooks/api/roles/queries.tsx @@ -94,10 +94,13 @@ export const useGetOrgRole = (orgId: string, roleId: string) => useQuery({ queryKey: roleQueryKeys.getOrgRole(orgId, roleId), queryFn: async () => { - const { data } = await apiRequest.get<{ role: TProjectRole }>( - `/api/v1/organization/${orgId}/roles/${roleId}` // TODO: implement - ); - return data.role; + const { data } = await apiRequest.get<{ + role: Omit & { permissions: unknown }; + }>(`/api/v1/organization/${orgId}/roles/${roleId}`); + return { + ...data.role, + permissions: unpackRules(data.role.permissions as PackRule[]) + }; }, enabled: Boolean(orgId && roleId) }); diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx index 32bbe4786..9c8bb4da2 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx @@ -106,8 +106,6 @@ const SIMPLE_PERMISSION_OPTIONS = [ export const OrgRoleModifySection = ({ role, onGoBack }: Props) => { const isNonEditable = ["owner", "admin", "member", "no-access"].includes(role?.slug || ""); const isNewRole = !role?.slug; - - const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; const { diff --git a/frontend/src/views/Org/RolePage/RolePage.tsx b/frontend/src/views/Org/RolePage/RolePage.tsx index d939a28b9..4480a950e 100644 --- a/frontend/src/views/Org/RolePage/RolePage.tsx +++ b/frontend/src/views/Org/RolePage/RolePage.tsx @@ -18,29 +18,15 @@ import { } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { withPermission } from "@app/hoc"; -import { - // useDeleteIdentity, - // useGetIdentityById, - // useRevokeIdentityTokenAuthToken, - // useRevokeIdentityUniversalAuthClientSecret, - useGetOrgRole -} from "@app/hooks/api"; +import { useGetOrgRole } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; import { RolePermissionsTable } from "./components/RolePermissionsSection/RolePermissionsTable"; -import { RoleDetailsSection, RolePermissionsSection } from "./components"; - -// import { IdentityAuthMethodModal } from "../MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal"; -// import { IdentityModal } from "../MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal"; -// import { IdentityUniversalAuthClientSecretModal } from "../MembersPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal"; -// import { -// IdentityAuthenticationSection, -// IdentityClientSecretModal, -// IdentityDetailsSection, -// IdentityProjectsSection, -// IdentityTokenListModal, -// IdentityTokenModal -// } from "./components"; +import { + RoleDetailsSection, + RoleModal, + RolePermissionModal, + RolePermissionsSection} from "./components"; export const RolePage = withPermission( () => { @@ -57,17 +43,8 @@ export const RolePage = withPermission( // const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ - "identity", - "deleteIdentity", - "identityAuthMethod", - "revokeAuthMethod", - "token", - "tokenList", - "revokeToken", - "clientSecret", - "revokeClientSecret", - "universalAuthClientSecret", // list of client secrets - "upgradePlan" + "role", + "rolePermission" ] as const); // const onDeleteIdentitySubmit = async (id: string) => { @@ -202,10 +179,12 @@ export const RolePage = withPermission(
- +
)} + + {/* , data?: {}) => void; + handlePopUpOpen: (popUpName: keyof UsePopUpState<["role"]>, data?: {}) => void; }; -export const RoleDetailsSection = ({ roleId }: Props) => { +export const RoleDetailsSection = ({ roleId, handlePopUpOpen }: Props) => { const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset({ initialState: "Copy ID to clipboard" }); @@ -22,8 +22,6 @@ export const RoleDetailsSection = ({ roleId }: Props) => { const orgId = currentOrg?.id || ""; const { data } = useGetOrgRole(orgId, roleId); - console.log("useGetOrgRole data: ", data); - return data ? (
@@ -37,14 +35,11 @@ export const RoleDetailsSection = ({ roleId }: Props) => { ariaLabel="copy icon" variant="plain" className="group relative" - onClick={() => { - // handlePopUpOpen("identity", { - // identityId, - // name: data.identity.name, - // role: data.role, - // customRole: data.customRole - // }); - }} + onClick={() => + handlePopUpOpen("role", { + roleId + }) + } > @@ -79,6 +74,14 @@ export const RoleDetailsSection = ({ roleId }: Props) => {

Name

{data.name}

+
+

Slug

+

{data.slug}

+
+
+

Description

+

{data.description}

+
) : ( diff --git a/frontend/src/views/Org/RolePage/components/RoleDetailsModal.tsx b/frontend/src/views/Org/RolePage/components/RoleModal.tsx similarity index 71% rename from frontend/src/views/Org/RolePage/components/RoleDetailsModal.tsx rename to frontend/src/views/Org/RolePage/components/RoleModal.tsx index 68f6f2b75..28816b6fa 100644 --- a/frontend/src/views/Org/RolePage/components/RoleDetailsModal.tsx +++ b/frontend/src/views/Org/RolePage/components/RoleModal.tsx @@ -1,4 +1,4 @@ -// import { useEffect } from "react"; +import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; @@ -7,11 +7,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; import { useOrganization } from "@app/context"; -import { - useGetOrgRole - // useCreateOrgRole, - // useUpdateOrgRole -} from "@app/hooks/api"; +import { useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = z @@ -37,26 +33,22 @@ type Props = { handlePopUpToggle: (popUpName: keyof UsePopUpState<["role"]>, state?: boolean) => void; }; -export const RoleDetailsModal = ({ popUp, handlePopUpToggle }: Props) => { +export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => { // const router = useRouter(); const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; - const role = popUp?.role?.data as { + const popupData = popUp?.role?.data as { roleId: string; }; - const { data } = useGetOrgRole(orgId, role.roleId ?? ""); - console.log("RoleDetailsModal: useGetOrgRole data: ", data); - - // TODO: fetch by role id here + const { data: role } = useGetOrgRole(orgId, popupData?.roleId ?? ""); // const { mutateAsync: createMutateAsync } = useCreateIdentity(); // const { mutateAsync: updateMutateAsync } = useUpdateIdentity(); // const { mutateAsync: addMutateAsync } = useAddIdentityUniversalAuth(); - // const { mutateAsync: createOrgRole } = useCreateOrgRole(); - // const { mutateAsync: updateOrgRole } = useUpdateOrgRole(); + const { mutateAsync: updateOrgRole } = useUpdateOrgRole(); const { control, @@ -71,21 +63,21 @@ export const RoleDetailsModal = ({ popUp, handlePopUpToggle }: Props) => { } }); - // useEffect(() => { - // if (role) { - // reset({ - // name: role.name, - // description: role.description, - // slug: role.slug - // }); - // } else { - // reset({ - // name: "", - // description: "", - // slug: "" - // }); - // } - // }, [role]); + useEffect(() => { + if (role) { + reset({ + name: role.name, + description: role.description, + slug: role.slug + }); + } else { + reset({ + name: "", + description: "", + slug: "" + }); + } + }, [role]); const onFormSubmit = async ({ name, description, slug }: FormData) => { try { @@ -95,23 +87,22 @@ export const RoleDetailsModal = ({ popUp, handlePopUpToggle }: Props) => { slug }); + if (!orgId) return; + if (role) { // update - // const up = await updateOrgRole({}); - - // console.log("onFormSubmit up: ", up); - - // await updateMutateAsync({ - // identityId: identity.identityId, - // name, - // role: role || undefined, - // organizationId: orgId - // }); + await updateOrgRole({ + orgId, + id: role.id, + name, + description, + slug + }); handlePopUpToggle("role", false); } else { - // create + // TODO: create // const { id: createdId } = await createMutateAsync({ // name, @@ -119,16 +110,6 @@ export const RoleDetailsModal = ({ popUp, handlePopUpToggle }: Props) => { // organizationId: orgId // }); - // await addMutateAsync({ - // organizationId: orgId, - // identityId: createdId, - // clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], - // accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], - // accessTokenTTL: 2592000, - // accessTokenMaxTTL: 2592000, - // accessTokenNumUsesLimit: 0 - // }); - handlePopUpToggle("role", false); // router.push(`/org/${orgId}/identities/${createdId}`); } @@ -168,7 +149,12 @@ export const RoleDetailsModal = ({ popUp, handlePopUpToggle }: Props) => { defaultValue="" name="name" render={({ field, fieldState: { error } }) => ( - + )} @@ -178,7 +164,12 @@ export const RoleDetailsModal = ({ popUp, handlePopUpToggle }: Props) => { defaultValue="" name="slug" render={({ field, fieldState: { error } }) => ( - + )} diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionModal.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionModal.tsx new file mode 100644 index 000000000..18347bd57 --- /dev/null +++ b/frontend/src/views/Org/RolePage/components/RolePermissionModal.tsx @@ -0,0 +1,350 @@ +import { useEffect } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Checkbox, + FormControl, + Modal, + ModalContent, + Select, + SelectItem +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { useGetOrgRole } from "@app/hooks/api"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +enum Permission { + NoAccess = "no-access", + ReadOnly = "read-only", + FullAccess = "full-acess", + Custom = "custom" +} + +const generalPermissionSchema = z + .object({ + read: z.boolean().optional(), + edit: z.boolean().optional(), + delete: z.boolean().optional(), + create: z.boolean().optional() + }) + .optional(); + +const specificPermissionSchemas = { + workspace: z + .object({ + read: z.boolean().optional(), + create: z.boolean().optional() + }) + .optional(), + member: generalPermissionSchema, + groups: generalPermissionSchema, + role: generalPermissionSchema, + settings: generalPermissionSchema, + "service-account": generalPermissionSchema, + "incident-contact": generalPermissionSchema, + "secret-scanning": generalPermissionSchema, + sso: generalPermissionSchema, + scim: generalPermissionSchema, + ldap: generalPermissionSchema, + billing: generalPermissionSchema, + identity: generalPermissionSchema +}; + +// Create a union of all possible keys +const permissionsUnion = z.union([ + z.object({ workspace: specificPermissionSchemas.workspace }), + z.object({ member: specificPermissionSchemas.member }), + z.object({ groups: specificPermissionSchemas.groups }), + z.object({ role: specificPermissionSchemas.role }), + z.object({ settings: specificPermissionSchemas.settings }), + z.object({ "service-account": specificPermissionSchemas["service-account"] }), + z.object({ "incident-contact": specificPermissionSchemas["incident-contact"] }), + z.object({ "secret-scanning": specificPermissionSchemas["secret-scanning"] }), + z.object({ sso: specificPermissionSchemas.sso }), + z.object({ scim: specificPermissionSchemas.scim }), + z.object({ ldap: specificPermissionSchemas.ldap }), + z.object({ billing: specificPermissionSchemas.billing }), + z.object({ identity: specificPermissionSchemas.identity }) +]); + +const schema = z.object({ + resource: z.string(), // this is formName + action: z.nativeEnum(Permission), + permissions: z.record(z.string(), permissionsUnion).optional() +}); + +type FormData = z.infer; + +type Props = { + roleId: string; + popUp: UsePopUpState<["rolePermission"]>; + handlePopUpToggle: (popUpName: keyof UsePopUpState<["rolePermission"]>, state?: boolean) => void; +}; + +const SIMPLE_PERMISSION_OPTIONS = [ + { + title: "User management", + formName: "member" + }, + { + title: "Group management", + formName: "groups" + }, + { + title: "Machine identity management", + formName: "identity" + }, + { + title: "Billing & usage", + formName: "billing" + }, + { + title: "Role management", + formName: "role" + }, + { + title: "Incident Contacts", + formName: "incident-contact" + }, + { + title: "Organization profile", + formName: "settings" + }, + { + title: "Secret Scanning", + formName: "secret-scanning" + }, + { + title: "SSO", + formName: "sso" + }, + { + title: "LDAP", + formName: "ldap" + }, + { + title: "SCIM", + formName: "scim" + } +] as const; + +const PERMISSIONS = [ + { action: "read", label: "View" }, + { action: "create", label: "Create" }, + { action: "edit", label: "Modify" }, + { action: "delete", label: "Remove" } +] as const; + +const SECRET_SCANNING_PERMISSIONS = [ + { action: "read", label: "View risks" }, + { action: "create", label: "Add integrations" }, + { action: "edit", label: "Edit risk status" }, + { action: "delete", label: "Remove integrations" } +] as const; + +const INCIDENT_CONTACTS_PERMISSIONS = [ + { action: "read", label: "View contacts" }, + { action: "create", label: "Add new contacts" }, + { action: "edit", label: "Edit contacts" }, + { action: "delete", label: "Remove contacts" } +] as const; + +const MEMBERS_PERMISSIONS = [ + { action: "read", label: "View all members" }, + { action: "create", label: "Invite members" }, + { action: "edit", label: "Edit members" }, + { action: "delete", label: "Remove members" } +] as const; + +const BILLING_PERMISSIONS = [ + { action: "read", label: "View bills" }, + { action: "create", label: "Add payment methods" }, + { action: "edit", label: "Edit payments" }, + { action: "delete", label: "Remove payments" } +] as const; + +const getPermissionList = (option: string) => { + switch (option) { + case "secret-scanning": + return SECRET_SCANNING_PERMISSIONS; + case "billing": + return BILLING_PERMISSIONS; + case "incident-contact": + return INCIDENT_CONTACTS_PERMISSIONS; + case "member": + return MEMBERS_PERMISSIONS; + default: + return PERMISSIONS; + } +}; + +export const RolePermissionModal = ({ roleId, popUp, handlePopUpToggle }: Props) => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + const { data: role } = useGetOrgRole(orgId, roleId); + + const { + control, + handleSubmit, + reset, + formState: { isSubmitting }, + watch + } = useForm({ + resolver: zodResolver(schema) + }); + + const resource = watch("resource"); + const action = watch("action"); + + useEffect(() => { + reset({ + resource: SIMPLE_PERMISSION_OPTIONS[0].formName, + action: Permission.NoAccess + }); + + // TODO: update for existing permission + + // if (role) { + // console.log("existing role found: ", role); + // reset({ + // resource: SIMPLE_PERMISSION_OPTIONS[0].formName + // }); + // } else { + // console.log("no role found"); + // reset({ + // resource: SIMPLE_PERMISSION_OPTIONS[0].formName + // }); + // } + }, [role]); + + const onFormSubmit = async () => { + try { + // TODO: map action to permission array? + + // TODO: add permission to role + + // await addIdentityToWorkspace({ + // workspaceId, + // identityId, + // role: role || undefined + // }); + + createNotification({ + text: "Successfully added permission to role", + type: "success" + }); + + // reset(); + // handlePopUpToggle("rolePermission", false); + } catch (err) { + console.error(err); + const error = err as any; + const text = error?.response?.data?.message ?? "Failed to add identity to project"; + + createNotification({ + text, + type: "error" + }); + } + }; + + return ( + { + handlePopUpToggle("rolePermission", isOpen); + reset(); + }} + > + +
+ ( + + + + )} + /> + ( + + + + )} + /> + {action === Permission.Custom && ( +
+ {getPermissionList(watch("resource")).map((p) => { + return ( + ( + + {p.label} + + )} + /> + ); + })} +
+ )} +
+ + +
+ +
+
+ ); +}; diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow2.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow2.tsx new file mode 100644 index 000000000..2d56f1e47 --- /dev/null +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow2.tsx @@ -0,0 +1,215 @@ +import { useEffect, useMemo } from "react"; +import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form"; +import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { createNotification } from "@app/components/notifications"; +import { Checkbox, IconButton, Select, SelectItem,Td, Tr } from "@app/components/v2"; +import { useToggle } from "@app/hooks"; +import { TFormSchema } from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils"; + +const PERMISSIONS = [ + { action: "read", label: "View" }, + { action: "create", label: "Create" }, + { action: "edit", label: "Modify" }, + { action: "delete", label: "Remove" } +] as const; + +const SECRET_SCANNING_PERMISSIONS = [ + { action: "read", label: "View risks" }, + { action: "create", label: "Add integrations" }, + { action: "edit", label: "Edit risk status" }, + { action: "delete", label: "Remove integrations" } +] as const; + +const INCIDENT_CONTACTS_PERMISSIONS = [ + { action: "read", label: "View contacts" }, + { action: "create", label: "Add new contacts" }, + { action: "edit", label: "Edit contacts" }, + { action: "delete", label: "Remove contacts" } +] as const; + +const MEMBERS_PERMISSIONS = [ + { action: "read", label: "View all members" }, + { action: "create", label: "Invite members" }, + { action: "edit", label: "Edit members" }, + { action: "delete", label: "Remove members" } +] as const; + +const BILLING_PERMISSIONS = [ + { action: "read", label: "View bills" }, + { action: "create", label: "Add payment methods" }, + { action: "edit", label: "Edit payments" }, + { action: "delete", label: "Remove payments" } +] as const; + +const getPermissionList = (option: string) => { + switch (option) { + case "secret-scanning": + return SECRET_SCANNING_PERMISSIONS; + case "billing": + return BILLING_PERMISSIONS; + case "incident-contact": + return INCIDENT_CONTACTS_PERMISSIONS; + case "member": + return MEMBERS_PERMISSIONS; + default: + return PERMISSIONS; + } +}; + +type Props = { + title: string; + formName: keyof Omit, "workspace">; + setValue: UseFormSetValue; + control: Control; +}; + +// permission categories + +enum Permission { + NoAccess = "no-access", + ReadOnly = "read-only", + FullAccess = "full-acess", + Custom = "custom" +} + +// TODO: support for default roles + +export const RolePermissionRow2 = ({ title, formName, control, setValue }: Props) => { + const [isRowExpanded, setIsRowExpanded] = useToggle(); + const [isCustom, setIsCustom] = useToggle(); + + const rule = useWatch({ + control, + name: `permissions.${formName}` + }); + + const selectedPermissionCategory = useMemo(() => { + const actions = Object.keys(rule || {}) as Array; + const totalActions = PERMISSIONS.length; + const score = actions.map((key) => (rule?.[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); + + if (isCustom) return Permission.Custom; + if (score === 0) return Permission.NoAccess; + if (score === totalActions) return Permission.FullAccess; + if (score === 1 && rule?.read) return Permission.ReadOnly; + + return Permission.Custom; + }, [rule, isCustom]); + + useEffect(() => { + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + }, [selectedPermissionCategory]); + + useEffect(() => { + const isRowCustom = selectedPermissionCategory === Permission.Custom; + if (isRowCustom) { + setIsRowExpanded.on(); + } + }, []); + + const handlePermissionChange = (val: Permission) => { + // TODO: trigger update + if (val === Permission.Custom) { + setIsRowExpanded.on(); + setIsCustom.on(); + return; + } + setIsCustom.off(); + + switch (val) { + case Permission.NoAccess: + setValue( + `permissions.${formName}`, + { read: false, edit: false, create: false, delete: false }, + { shouldDirty: true } + ); + break; + case Permission.FullAccess: + setValue( + `permissions.${formName}`, + { read: true, edit: true, create: true, delete: true }, + { shouldDirty: true } + ); + break; + case Permission.ReadOnly: + setValue( + `permissions.${formName}`, + { read: true, edit: false, create: false, delete: false }, + { shouldDirty: true } + ); + break; + default: + setValue( + `permissions.${formName}`, + { read: false, edit: false, create: false, delete: false }, + { shouldDirty: true } + ); + break; + } + + createNotification({ type: "success", text: "Updated permission on role." }); + }; + + return ( + <> + + + setIsRowExpanded.toggle()} + > + + + + {title} + + + + + {isRowExpanded && ( + + +
+ {getPermissionList(formName).map(({ action, label }) => { + return ( + ( + + {label} + + )} + /> + ); + })} +
+ + + )} + + ); +}; diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx index 347fb6ba2..6f5220242 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -1,20 +1,15 @@ -import { faPlus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +// import { UsePopUpState } from "@app/hooks/usePopUp"; +import { RolePermissionsTable2 } from "./RolePermissionsTable2"; -// import { createNotification } from "@app/components/notifications"; -import { - // DeleteActionModal, - IconButton -} from "@app/components/v2"; +type Props = { + roleId: string; + // handlePopUpOpen: (popUpName: keyof UsePopUpState<["rolePermission"]>, data?: {}) => void; +}; -import { RolePermissionsTable } from "./RolePermissionsTable"; -// import { useDeleteIdentityFromWorkspace } from "@app/hooks/api"; -// import { usePopUp } from "@app/hooks/usePopUp"; - -// import { IdentityAddToProjectModal } from "./IdentityAddToProjectModal"; -// import { IdentityProjectsTable } from "./IdentityProjectsTable"; - -export const RolePermissionsSection = () => { +export const RolePermissionsSection = ({ + roleId +}: // handlePopUpOpen +Props) => { // const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace(); // const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ @@ -51,20 +46,18 @@ export const RolePermissionsSection = () => {

Permissions

- { - console.log("TODO"); - // handlePopUpOpen("addIdentityToProject"); - }} + onClick={() => handlePopUpOpen("rolePermission")} > - + */}
- + {/* */} + {/* */}
{/* { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + + const { data: role } = useGetOrgRole(orgId, roleId); + + const { setValue, control } = useForm({ + defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {}, + resolver: zodResolver(formSchema) + }); + + return ( + + + + + + + + + + {SIMPLE_PERMISSION_OPTIONS.map((permission) => { + return ( + + ); + })} + +
+ ResourcePermission
+
+ ); +}; diff --git a/frontend/src/views/Org/RolePage/components/index.tsx b/frontend/src/views/Org/RolePage/components/index.tsx index dadfb93af..dd44ac103 100644 --- a/frontend/src/views/Org/RolePage/components/index.tsx +++ b/frontend/src/views/Org/RolePage/components/index.tsx @@ -1,2 +1,4 @@ export { RoleDetailsSection } from "./RoleDetailsSection"; +export { RoleModal } from "./RoleModal"; +export { RolePermissionModal } from "./RolePermissionModal"; export { RolePermissionsSection } from "./RolePermissionsSection"; diff --git a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx index ede481821..8cf1723cc 100644 --- a/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx +++ b/frontend/src/views/SecretOverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx @@ -161,7 +161,6 @@ export const SecretOverviewTableRow = ({ secretPath={secretPath} getSecretByKey={getSecretByKey} /> - From b64d4e57c49e2d3b63fb8196ceff6712711eae93 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 22 Jul 2024 16:56:27 +0700 Subject: [PATCH 06/10] Clean org roles concept refactor --- frontend/src/hooks/api/roles/mutation.tsx | 44 ++- .../OrgRoleTabSection/OrgRoleTabSection.tsx | 4 +- .../OrgRoleTabSection/OrgRoleTable.tsx | 22 +- frontend/src/views/Org/RolePage/RolePage.tsx | 188 ++-------- .../components/RoleDetailsSection.tsx | 4 +- .../Org/RolePage/components/RoleModal.tsx | 35 +- .../components/RolePermissionModal.tsx | 350 ------------------ ...rmissionRow2.tsx => RolePermissionRow.tsx} | 19 +- .../RolePermissionsSection.tsx | 76 +--- .../RolePermissionsTable.tsx | 223 +++++------ .../RolePermissionsTable2.tsx | 103 ------ .../views/Org/RolePage/components/index.tsx | 1 - 12 files changed, 222 insertions(+), 847 deletions(-) delete mode 100644 frontend/src/views/Org/RolePage/components/RolePermissionModal.tsx rename frontend/src/views/Org/RolePage/components/RolePermissionsSection/{RolePermissionRow2.tsx => RolePermissionRow.tsx} (91%) delete mode 100644 frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable2.tsx diff --git a/frontend/src/hooks/api/roles/mutation.tsx b/frontend/src/hooks/api/roles/mutation.tsx index db94c7403..ef330e053 100644 --- a/frontend/src/hooks/api/roles/mutation.tsx +++ b/frontend/src/hooks/api/roles/mutation.tsx @@ -9,6 +9,7 @@ import { TCreateProjectRoleDTO, TDeleteOrgRoleDTO, TDeleteProjectRoleDTO, + TOrgRole, TUpdateOrgRoleDTO, TUpdateProjectRoleDTO } from "./types"; @@ -52,12 +53,17 @@ export const useDeleteProjectRole = () => { export const useCreateOrgRole = () => { const queryClient = useQueryClient(); - return useMutation({ - mutationFn: ({ orgId, permissions, ...dto }: TCreateOrgRoleDTO) => - apiRequest.post(`/api/v1/organization/${orgId}/roles`, { + return useMutation({ + mutationFn: async ({ orgId, permissions, ...dto }: TCreateOrgRoleDTO) => { + const { + data: { role } + } = await apiRequest.post(`/api/v1/organization/${orgId}/roles`, { ...dto, permissions: permissions.length ? packRules(permissions) : [] - }), + }); + + return role; + }, onSuccess: (_, { orgId }) => { queryClient.invalidateQueries(roleQueryKeys.getOrgRoles(orgId)); } @@ -67,19 +73,16 @@ export const useCreateOrgRole = () => { export const useUpdateOrgRole = () => { const queryClient = useQueryClient(); - return useMutation({ - mutationFn: ({ id, orgId, permissions, ...dto }: TUpdateOrgRoleDTO) => { - console.log("update args: ", { - id, - orgId, - permissions, - ...dto, - pack: permissions?.length ? packRules(permissions) : [] - }); - return apiRequest.patch(`/api/v1/organization/${orgId}/roles/${id}`, { + return useMutation({ + mutationFn: async ({ id, orgId, permissions, ...dto }: TUpdateOrgRoleDTO) => { + const { + data: { role } + } = await apiRequest.patch(`/api/v1/organization/${orgId}/roles/${id}`, { ...dto, permissions: permissions?.length ? packRules(permissions) : [] }); + + return role; }, onSuccess: (_, { id, orgId }) => { queryClient.invalidateQueries(roleQueryKeys.getOrgRoles(orgId)); @@ -91,11 +94,16 @@ export const useUpdateOrgRole = () => { export const useDeleteOrgRole = () => { const queryClient = useQueryClient(); - return useMutation({ - mutationFn: ({ orgId, id }: TDeleteOrgRoleDTO) => - apiRequest.delete(`/api/v1/organization/${orgId}/roles/${id}`, { + return useMutation({ + mutationFn: async ({ orgId, id }: TDeleteOrgRoleDTO) => { + const { + data: { role } + } = await apiRequest.delete(`/api/v1/organization/${orgId}/roles/${id}`, { data: { orgId } - }), + }); + + return role; + }, onSuccess: (_, { id, orgId }) => { queryClient.invalidateQueries(roleQueryKeys.getOrgRoles(orgId)); queryClient.invalidateQueries(roleQueryKeys.getOrgRole(orgId, id)); diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx index 2d4dcd358..ef83bca9c 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx @@ -7,7 +7,7 @@ import { OrgRoleModifySection } from "./OrgRoleModifySection"; import { OrgRoleTable } from "./OrgRoleTable"; export const OrgRoleTabSection = () => { - const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["editRole"] as const); + const { popUp, handlePopUpClose } = usePopUp(["editRole"] as const); return popUp.editRole.isOpen ? ( { animate={{ opacity: 1, translateX: 0 }} exit={{ opacity: 0, translateX: -30 }} > - handlePopUpOpen("editRole", role)} /> + ); }; diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx index dab3e8e74..904aec264 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx @@ -25,17 +25,15 @@ import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@a import { usePopUp } from "@app/hooks"; import { useDeleteOrgRole, useGetOrgRoles } from "@app/hooks/api"; import { TOrgRole } from "@app/hooks/api/roles/types"; +import { RoleModal } from "@app/views/Org/RolePage/components"; -type Props = { - onSelectRole: (role?: TOrgRole) => void; -}; - -export const OrgRoleTable = ({ onSelectRole }: Props) => { +export const OrgRoleTable = () => { const router = useRouter(); const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ + "role", "deleteRole" ] as const); @@ -68,7 +66,10 @@ export const OrgRoleTable = ({ onSelectRole }: Props) => { colorSchema="primary" type="submit" leftIcon={} - onClick={() => onSelectRole()} + // onClick={() => onSelectRole()} + onClick={() => { + handlePopUpOpen("role"); + }} isDisabled={!isAllowed} > Add Role @@ -119,7 +120,8 @@ export const OrgRoleTable = ({ onSelectRole }: Props) => { )} onClick={(e) => { e.stopPropagation(); - onSelectRole(role); + router.push(`/org/${orgId}/roles/${id}`); + // onSelectRole(role); }} disabled={!isAllowed} > @@ -142,7 +144,10 @@ export const OrgRoleTable = ({ onSelectRole }: Props) => { ? "hover:!bg-red-500 hover:!text-white" : "pointer-events-none cursor-not-allowed opacity-50" )} - onClick={() => handlePopUpOpen("deleteRole", role)} + onClick={(e) => { + e.stopPropagation(); + handlePopUpOpen("deleteRole", role); + }} disabled={!isAllowed} > Delete Role @@ -158,6 +163,7 @@ export const OrgRoleTable = ({ onSelectRole }: Props) => {
+ { @@ -35,76 +29,40 @@ export const RolePage = withPermission( const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; const { data } = useGetOrgRole(orgId, roleId); - console.log("useGetOrgRole data: ", data); - - // const { data } = useGetIdentityById(identityId); // TODO: get role by id - // const { mutateAsync: deleteIdentity } = useDeleteIdentity(); - // const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken(); - // const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret(); + const { mutateAsync: deleteOrgRole } = useDeleteOrgRole(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "role", - "rolePermission" + "deleteOrgRole" ] as const); - // const onDeleteIdentitySubmit = async (id: string) => { - // try { - // await deleteIdentity({ - // identityId: id, - // organizationId: orgId - // }); + const onDeleteOrgRoleSubmit = async () => { + try { + if (!orgId || !roleId) return; - // createNotification({ - // text: "Successfully deleted identity", - // type: "success" - // }); + await deleteOrgRole({ + orgId, + id: roleId + }); - // handlePopUpClose("deleteIdentity"); - // router.push(`/org/${orgId}/members`); - // } catch (err) { - // console.error(err); - // const error = err as any; - // const text = error?.response?.data?.message ?? "Failed to delete identity"; + createNotification({ + text: "Successfully deleted organization role", + type: "success" + }); - // createNotification({ - // text, - // type: "error" - // }); - // } - // }; + handlePopUpClose("deleteOrgRole"); + router.push(`/org/${orgId}/members`); + } catch (err) { + console.error(err); + const error = err as any; + const text = error?.response?.data?.message ?? "Failed to delete organization role"; - // const onRevokeTokenSubmit = async ({ - // identityId: parentIdentityId, - // tokenId, - // name - // }: { - // identityId: string; - // tokenId: string; - // name: string; - // }) => { - // try { - // await revokeToken({ - // identityId: parentIdentityId, - // tokenId - // }); - - // handlePopUpClose("revokeToken"); - - // createNotification({ - // text: `Successfully revoked token ${name ?? ""}`, - // type: "success" - // }); - // } catch (err) { - // console.error(err); - // const error = err as any; - // const text = error?.response?.data?.message ?? "Failed to delete identity"; - - // createNotification({ - // text, - // type: "error" - // }); - // } - // }; + createNotification({ + text, + type: "error" + }); + } + }; return (
@@ -139,12 +97,9 @@ export const RolePage = withPermission( !isAllowed && "pointer-events-none cursor-not-allowed opacity-50" )} onClick={async () => { - // handlePopUpOpen("identity", { - // identityId, - // name: data.identity.name, - // role: data.role, - // customRole: data.customRole - // }); + handlePopUpOpen("role", { + roleId + }); }} disabled={!isAllowed} > @@ -161,10 +116,7 @@ export const RolePage = withPermission( : "pointer-events-none cursor-not-allowed opacity-50" )} onClick={async () => { - // handlePopUpOpen("deleteIdentity", { - // identityId, - // name: data.identity.name - // }); + handlePopUpOpen("deleteOrgRole"); }} disabled={!isAllowed} > @@ -179,84 +131,18 @@ export const RolePage = withPermission(
- +
)} - - {/* - - - - - - handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} - /> handlePopUpToggle("deleteIdentity", isOpen)} + isOpen={popUp.deleteOrgRole.isOpen} + title={`Are you sure want to delete the organization role ${data?.name ?? ""}?`} + onChange={(isOpen) => handlePopUpToggle("deleteOrgRole", isOpen)} deleteKey="confirm" - onDeleteApproved={() => - onDeleteIdentitySubmit( - (popUp?.deleteIdentity?.data as { identityId: string })?.identityId - ) - } + onDeleteApproved={() => onDeleteOrgRoleSubmit()} /> - handlePopUpToggle("revokeToken", isOpen)} - deleteKey="confirm" - onDeleteApproved={() => { - const revokeTokenData = popUp?.revokeToken?.data as { - identityId: string; - tokenId: string; - name: string; - }; - - return onRevokeTokenSubmit(revokeTokenData); - }} - /> - handlePopUpToggle("revokeClientSecret", isOpen)} - deleteKey="confirm" - onDeleteApproved={() => { - const deleteClientSecretData = popUp?.revokeClientSecret?.data as { - clientSecretId: string; - clientSecretPrefix: string; - }; - - return onDeleteClientSecretSubmit({ - clientSecretId: deleteClientSecretData.clientSecretId - }); - }} - /> */}
); }, diff --git a/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx b/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx index cad447b8e..4142569fd 100644 --- a/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx +++ b/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx @@ -80,7 +80,9 @@ export const RoleDetailsSection = ({ roleId, handlePopUpOpen }: Props) => {

Description

-

{data.description}

+

+ {data.description?.length ? data.description : "-"} +

diff --git a/frontend/src/views/Org/RolePage/components/RoleModal.tsx b/frontend/src/views/Org/RolePage/components/RoleModal.tsx index 28816b6fa..ab909d931 100644 --- a/frontend/src/views/Org/RolePage/components/RoleModal.tsx +++ b/frontend/src/views/Org/RolePage/components/RoleModal.tsx @@ -1,13 +1,13 @@ import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; +import { useRouter } from "next/router"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; -// import { useRouter } from "next/router"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; import { useOrganization } from "@app/context"; -import { useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api"; +import { useCreateOrgRole, useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = z @@ -22,19 +22,11 @@ export type FormData = z.infer; type Props = { popUp: UsePopUpState<["role"]>; - // handlePopUpOpen: ( - // popUpName: keyof UsePopUpState<["identityAuthMethod"]>, - // data: { - // identityId: string; - // name: string; - // authMethod?: IdentityAuthMethod; - // } - // ) => void; handlePopUpToggle: (popUpName: keyof UsePopUpState<["role"]>, state?: boolean) => void; }; export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => { - // const router = useRouter(); + const router = useRouter(); const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; @@ -44,10 +36,7 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => { const { data: role } = useGetOrgRole(orgId, popupData?.roleId ?? ""); - // const { mutateAsync: createMutateAsync } = useCreateIdentity(); - // const { mutateAsync: updateMutateAsync } = useUpdateIdentity(); - // const { mutateAsync: addMutateAsync } = useAddIdentityUniversalAuth(); - + const { mutateAsync: createOrgRole } = useCreateOrgRole(); const { mutateAsync: updateOrgRole } = useUpdateOrgRole(); const { @@ -102,16 +91,18 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => { handlePopUpToggle("role", false); } else { - // TODO: create + // create - // const { id: createdId } = await createMutateAsync({ - // name, - // role: role || undefined, - // organizationId: orgId - // }); + const newRole = await createOrgRole({ + orgId, + name, + description, + slug, + permissions: [] + }); handlePopUpToggle("role", false); - // router.push(`/org/${orgId}/identities/${createdId}`); + router.push(`/org/${orgId}/roles/${newRole.id}`); } createNotification({ diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionModal.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionModal.tsx deleted file mode 100644 index 18347bd57..000000000 --- a/frontend/src/views/Org/RolePage/components/RolePermissionModal.tsx +++ /dev/null @@ -1,350 +0,0 @@ -import { useEffect } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; - -import { createNotification } from "@app/components/notifications"; -import { - Button, - Checkbox, - FormControl, - Modal, - ModalContent, - Select, - SelectItem -} from "@app/components/v2"; -import { useOrganization } from "@app/context"; -import { useGetOrgRole } from "@app/hooks/api"; -import { UsePopUpState } from "@app/hooks/usePopUp"; - -enum Permission { - NoAccess = "no-access", - ReadOnly = "read-only", - FullAccess = "full-acess", - Custom = "custom" -} - -const generalPermissionSchema = z - .object({ - read: z.boolean().optional(), - edit: z.boolean().optional(), - delete: z.boolean().optional(), - create: z.boolean().optional() - }) - .optional(); - -const specificPermissionSchemas = { - workspace: z - .object({ - read: z.boolean().optional(), - create: z.boolean().optional() - }) - .optional(), - member: generalPermissionSchema, - groups: generalPermissionSchema, - role: generalPermissionSchema, - settings: generalPermissionSchema, - "service-account": generalPermissionSchema, - "incident-contact": generalPermissionSchema, - "secret-scanning": generalPermissionSchema, - sso: generalPermissionSchema, - scim: generalPermissionSchema, - ldap: generalPermissionSchema, - billing: generalPermissionSchema, - identity: generalPermissionSchema -}; - -// Create a union of all possible keys -const permissionsUnion = z.union([ - z.object({ workspace: specificPermissionSchemas.workspace }), - z.object({ member: specificPermissionSchemas.member }), - z.object({ groups: specificPermissionSchemas.groups }), - z.object({ role: specificPermissionSchemas.role }), - z.object({ settings: specificPermissionSchemas.settings }), - z.object({ "service-account": specificPermissionSchemas["service-account"] }), - z.object({ "incident-contact": specificPermissionSchemas["incident-contact"] }), - z.object({ "secret-scanning": specificPermissionSchemas["secret-scanning"] }), - z.object({ sso: specificPermissionSchemas.sso }), - z.object({ scim: specificPermissionSchemas.scim }), - z.object({ ldap: specificPermissionSchemas.ldap }), - z.object({ billing: specificPermissionSchemas.billing }), - z.object({ identity: specificPermissionSchemas.identity }) -]); - -const schema = z.object({ - resource: z.string(), // this is formName - action: z.nativeEnum(Permission), - permissions: z.record(z.string(), permissionsUnion).optional() -}); - -type FormData = z.infer; - -type Props = { - roleId: string; - popUp: UsePopUpState<["rolePermission"]>; - handlePopUpToggle: (popUpName: keyof UsePopUpState<["rolePermission"]>, state?: boolean) => void; -}; - -const SIMPLE_PERMISSION_OPTIONS = [ - { - title: "User management", - formName: "member" - }, - { - title: "Group management", - formName: "groups" - }, - { - title: "Machine identity management", - formName: "identity" - }, - { - title: "Billing & usage", - formName: "billing" - }, - { - title: "Role management", - formName: "role" - }, - { - title: "Incident Contacts", - formName: "incident-contact" - }, - { - title: "Organization profile", - formName: "settings" - }, - { - title: "Secret Scanning", - formName: "secret-scanning" - }, - { - title: "SSO", - formName: "sso" - }, - { - title: "LDAP", - formName: "ldap" - }, - { - title: "SCIM", - formName: "scim" - } -] as const; - -const PERMISSIONS = [ - { action: "read", label: "View" }, - { action: "create", label: "Create" }, - { action: "edit", label: "Modify" }, - { action: "delete", label: "Remove" } -] as const; - -const SECRET_SCANNING_PERMISSIONS = [ - { action: "read", label: "View risks" }, - { action: "create", label: "Add integrations" }, - { action: "edit", label: "Edit risk status" }, - { action: "delete", label: "Remove integrations" } -] as const; - -const INCIDENT_CONTACTS_PERMISSIONS = [ - { action: "read", label: "View contacts" }, - { action: "create", label: "Add new contacts" }, - { action: "edit", label: "Edit contacts" }, - { action: "delete", label: "Remove contacts" } -] as const; - -const MEMBERS_PERMISSIONS = [ - { action: "read", label: "View all members" }, - { action: "create", label: "Invite members" }, - { action: "edit", label: "Edit members" }, - { action: "delete", label: "Remove members" } -] as const; - -const BILLING_PERMISSIONS = [ - { action: "read", label: "View bills" }, - { action: "create", label: "Add payment methods" }, - { action: "edit", label: "Edit payments" }, - { action: "delete", label: "Remove payments" } -] as const; - -const getPermissionList = (option: string) => { - switch (option) { - case "secret-scanning": - return SECRET_SCANNING_PERMISSIONS; - case "billing": - return BILLING_PERMISSIONS; - case "incident-contact": - return INCIDENT_CONTACTS_PERMISSIONS; - case "member": - return MEMBERS_PERMISSIONS; - default: - return PERMISSIONS; - } -}; - -export const RolePermissionModal = ({ roleId, popUp, handlePopUpToggle }: Props) => { - const { currentOrg } = useOrganization(); - const orgId = currentOrg?.id || ""; - const { data: role } = useGetOrgRole(orgId, roleId); - - const { - control, - handleSubmit, - reset, - formState: { isSubmitting }, - watch - } = useForm({ - resolver: zodResolver(schema) - }); - - const resource = watch("resource"); - const action = watch("action"); - - useEffect(() => { - reset({ - resource: SIMPLE_PERMISSION_OPTIONS[0].formName, - action: Permission.NoAccess - }); - - // TODO: update for existing permission - - // if (role) { - // console.log("existing role found: ", role); - // reset({ - // resource: SIMPLE_PERMISSION_OPTIONS[0].formName - // }); - // } else { - // console.log("no role found"); - // reset({ - // resource: SIMPLE_PERMISSION_OPTIONS[0].formName - // }); - // } - }, [role]); - - const onFormSubmit = async () => { - try { - // TODO: map action to permission array? - - // TODO: add permission to role - - // await addIdentityToWorkspace({ - // workspaceId, - // identityId, - // role: role || undefined - // }); - - createNotification({ - text: "Successfully added permission to role", - type: "success" - }); - - // reset(); - // handlePopUpToggle("rolePermission", false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to add identity to project"; - - createNotification({ - text, - type: "error" - }); - } - }; - - return ( - { - handlePopUpToggle("rolePermission", isOpen); - reset(); - }} - > - -
- ( - - - - )} - /> - ( - - - - )} - /> - {action === Permission.Custom && ( -
- {getPermissionList(watch("resource")).map((p) => { - return ( - ( - - {p.label} - - )} - /> - ); - })} -
- )} -
- - -
- -
-
- ); -}; diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow2.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx similarity index 91% rename from frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow2.tsx rename to frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx index 2d56f1e47..60c181d17 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow2.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx @@ -3,8 +3,7 @@ import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form" import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { createNotification } from "@app/components/notifications"; -import { Checkbox, IconButton, Select, SelectItem,Td, Tr } from "@app/components/v2"; +import { Checkbox, IconButton, Select, SelectItem, Td, Tr } from "@app/components/v2"; import { useToggle } from "@app/hooks"; import { TFormSchema } from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils"; @@ -63,6 +62,7 @@ type Props = { formName: keyof Omit, "workspace">; setValue: UseFormSetValue; control: Control; + handleSubmit: () => void; }; // permission categories @@ -76,7 +76,7 @@ enum Permission { // TODO: support for default roles -export const RolePermissionRow2 = ({ title, formName, control, setValue }: Props) => { +export const RolePermissionRow = ({ title, formName, handleSubmit, control, setValue }: Props) => { const [isRowExpanded, setIsRowExpanded] = useToggle(); const [isCustom, setIsCustom] = useToggle(); @@ -111,7 +111,6 @@ export const RolePermissionRow2 = ({ title, formName, control, setValue }: Props }, []); const handlePermissionChange = (val: Permission) => { - // TODO: trigger update if (val === Permission.Custom) { setIsRowExpanded.on(); setIsCustom.on(); @@ -150,12 +149,15 @@ export const RolePermissionRow2 = ({ title, formName, control, setValue }: Props break; } - createNotification({ type: "success", text: "Updated permission on role." }); + handleSubmit(); }; return ( <> - + setIsRowExpanded.toggle()} + > ( { + field.onChange(e); + handleSubmit(); + }} id={`permissions.${formName}.${action}`} > {label} diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx index 6f5220242..2b7d43a7d 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -1,88 +1,18 @@ -// import { UsePopUpState } from "@app/hooks/usePopUp"; -import { RolePermissionsTable2 } from "./RolePermissionsTable2"; +import { RolePermissionsTable } from "./RolePermissionsTable"; type Props = { roleId: string; - // handlePopUpOpen: (popUpName: keyof UsePopUpState<["rolePermission"]>, data?: {}) => void; }; -export const RolePermissionsSection = ({ - roleId -}: // handlePopUpOpen -Props) => { - // const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace(); - - // const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ - // "addIdentityToProject", - // "removeIdentityFromProject" - // ] as const); - - // const onRemoveIdentitySubmit = async (id: string, projectId: string) => { - // try { - // await deleteMutateAsync({ - // identityId: id, - // workspaceId: projectId - // }); - - // createNotification({ - // text: "Successfully removed identity from project", - // type: "success" - // }); - - // handlePopUpClose("removeIdentityFromProject"); - // } catch (err) { - // console.error(err); - // const error = err as any; - // const text = error?.response?.data?.message ?? "Failed to remove identity from project"; - - // createNotification({ - // text, - // type: "error" - // }); - // } - // }; - +export const RolePermissionsSection = ({ roleId }: Props) => { return (

Permissions

- {/* handlePopUpOpen("rolePermission")} - > - - */}
- {/* */} - - {/* */} +
- {/* handlePopUpToggle("removeIdentityFromProject", isOpen)} - deleteKey="confirm" - onDeleteApproved={() => { - const popupData = popUp?.removeIdentityFromProject?.data as { - identityId: string; - projectId: string; - }; - - return onRemoveIdentitySubmit(popupData.identityId, popupData.projectId); - }} - /> */} - {/* */}
); }; diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx index a4c7c9509..ac485b47b 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx @@ -1,124 +1,125 @@ -import { faEllipsis } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { twMerge } from "tailwind-merge"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; -import { OrgPermissionCan } from "@app/components/permissions"; +import { createNotification } from "@app/components/notifications"; +import { Table, TableContainer, TBody, Th, THead, Tr } from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - // Tooltip, - // IconButton, - // EmptyState, - Table, - TableContainer, - // TableSkeleton, - TBody, - Td, - Th, - THead, - Tr} from "@app/components/v2"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; -// import { UsePopUpState } from "@app/hooks/usePopUp"; + formRolePermission2API, + formSchema, + rolePermission2Form, + TFormSchema +} from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils"; -// import { IdentityProjectRow } from "./IdentityProjectRow"; +import { RolePermissionRow } from "./RolePermissionRow"; -// type Props = { -// identityId: string; -// handlePopUpOpen: ( -// popUpName: keyof UsePopUpState<["removeIdentityFromProject"]>, -// data?: {} -// ) => void; -// }; +const SIMPLE_PERMISSION_OPTIONS = [ + { + title: "User management", + formName: "member" + }, + { + title: "Group management", + formName: "groups" + }, + { + title: "Machine identity management", + formName: "identity" + }, + { + title: "Billing & usage", + formName: "billing" + }, + { + title: "Role management", + formName: "role" + }, + { + title: "Incident Contacts", + formName: "incident-contact" + }, + { + title: "Organization profile", + formName: "settings" + }, + { + title: "Secret Scanning", + formName: "secret-scanning" + }, + { + title: "SSO", + formName: "sso" + }, + { + title: "LDAP", + formName: "ldap" + }, + { + title: "SCIM", + formName: "scim" + } +] as const; + +type Props = { + roleId: string; +}; + +export const RolePermissionsTable = ({ roleId }: Props) => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + + const { data: role } = useGetOrgRole(orgId, roleId); + + const { setValue, control, handleSubmit } = useForm({ + defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {}, + resolver: zodResolver(formSchema) + }); + + const { mutateAsync: updateRole } = useUpdateOrgRole(); + + const onSubmit = async (el: TFormSchema) => { + try { + await updateRole({ + orgId, + id: roleId, + ...el, + permissions: formRolePermission2API(el.permissions) + }); + createNotification({ type: "success", text: "Successfully updated role" }); + } catch (err) { + console.log(err); + createNotification({ type: "error", text: "Failed to update role" }); + } + }; -export const RolePermissionsTable = () => { - // const { data: projectMemberships, isLoading } = useGetIdentityProjectMemberships(identityId); return ( - - - - - - - - - - - - - - {/* */} - {/* {isLoading && } */} - {/* {!isLoading && - projectMemberships?.map((membership) => { +
+
ResourceAllowed Actions -
IdentityCreate/Read - - -
- -
-
- - - {(isAllowed) => ( - { - e.stopPropagation(); - // TODO - }} - disabled={!isAllowed} - > - Edit Permission - - )} - - - {(isAllowed) => ( - { - e.stopPropagation(); - // handlePopUpOpen("deleteIdentity", { - // identityId: id, - // name - // }); - }} - disabled={!isAllowed} - > - Delete Permission - - )} - - -
-
+ + + + + + + + {SIMPLE_PERMISSION_OPTIONS.map((permission) => { return ( -
Row
- ); - })} */} - -
+ ResourcePermission
- {/* */} - {/* {!isLoading && !projectMemberships?.length && ( - - )} */} + })} + + +
); }; diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable2.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable2.tsx deleted file mode 100644 index aa55247e8..000000000 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable2.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; - -import { Table, TableContainer, TBody, Th, THead, Tr } from "@app/components/v2"; -import { useOrganization } from "@app/context"; -import { useGetOrgRole } from "@app/hooks/api"; -// TODO: consider moving this out -import { - formSchema, - rolePermission2Form, - TFormSchema} from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils"; - -import { RolePermissionRow2 } from "./RolePermissionRow2"; - -const SIMPLE_PERMISSION_OPTIONS = [ - { - title: "User management", - formName: "member" - }, - { - title: "Group management", - formName: "groups" - }, - { - title: "Machine identity management", - formName: "identity" - }, - { - title: "Billing & usage", - formName: "billing" - }, - { - title: "Role management", - formName: "role" - }, - { - title: "Incident Contacts", - formName: "incident-contact" - }, - { - title: "Organization profile", - formName: "settings" - }, - { - title: "Secret Scanning", - formName: "secret-scanning" - }, - { - title: "SSO", - formName: "sso" - }, - { - title: "LDAP", - formName: "ldap" - }, - { - title: "SCIM", - formName: "scim" - } -] as const; - -type Props = { - roleId: string; -}; - -export const RolePermissionsTable2 = ({ roleId }: Props) => { - const { currentOrg } = useOrganization(); - const orgId = currentOrg?.id || ""; - - const { data: role } = useGetOrgRole(orgId, roleId); - - const { setValue, control } = useForm({ - defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {}, - resolver: zodResolver(formSchema) - }); - - return ( - - - - - - - - - - {SIMPLE_PERMISSION_OPTIONS.map((permission) => { - return ( - - ); - })} - -
- ResourcePermission
-
- ); -}; diff --git a/frontend/src/views/Org/RolePage/components/index.tsx b/frontend/src/views/Org/RolePage/components/index.tsx index dd44ac103..3f57cd670 100644 --- a/frontend/src/views/Org/RolePage/components/index.tsx +++ b/frontend/src/views/Org/RolePage/components/index.tsx @@ -1,4 +1,3 @@ export { RoleDetailsSection } from "./RoleDetailsSection"; export { RoleModal } from "./RoleModal"; -export { RolePermissionModal } from "./RolePermissionModal"; export { RolePermissionsSection } from "./RolePermissionsSection"; From 6ba1012f5b5f97916ac2fe20d497fd2700027819 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 22 Jul 2024 18:55:34 +0700 Subject: [PATCH 07/10] Add default role support for RolePage --- backend/src/ee/routes/v1/org-role-router.ts | 1 - backend/src/services/org/org-role-service.ts | 47 +++++++++++++++++-- .../components/RoleDetailsSection.tsx | 45 +++++++++--------- .../RolePermissionRow.tsx | 30 ++++++++---- .../RolePermissionsTable.tsx | 3 ++ 5 files changed, 91 insertions(+), 35 deletions(-) diff --git a/backend/src/ee/routes/v1/org-role-router.ts b/backend/src/ee/routes/v1/org-role-router.ts index 559bd1611..ae7304907 100644 --- a/backend/src/ee/routes/v1/org-role-router.ts +++ b/backend/src/ee/routes/v1/org-role-router.ts @@ -53,7 +53,6 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }); server.route({ - // new: note that doesn't work for default roles method: "GET", url: "/:organizationId/roles/:roleId", config: { diff --git a/backend/src/services/org/org-role-service.ts b/backend/src/services/org/org-role-service.ts index 262f98bc6..26cfd67eb 100644 --- a/backend/src/services/org/org-role-service.ts +++ b/backend/src/services/org/org-role-service.ts @@ -51,9 +51,50 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol ) => { const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role); - const role = await orgRoleDAL.findOne({ id: roleId, orgId }); - if (!role) throw new BadRequestError({ message: "Role not found", name: "Get role" }); - return role; + + switch (roleId) { + case "b11b49a9-09a9-4443-916a-4246f9ff2c69": { + return { + id: roleId, + orgId, + name: "Admin", + slug: "admin", + description: "Complete administration access over the organization", + permissions: packRules(orgAdminPermissions.rules), + createdAt: new Date(), + updatedAt: new Date() + }; + } + case "b11b49a9-09a9-4443-916a-4246f9ff2c70": { + return { + id: roleId, + orgId, + name: "Member", + slug: "member", + description: "Non-administrative role in an organization", + permissions: packRules(orgMemberPermissions.rules), + createdAt: new Date(), + updatedAt: new Date() + }; + } + case "b10d49a9-09a9-4443-916a-4246f9ff2c72": { + return { + id: "b10d49a9-09a9-4443-916a-4246f9ff2c72", // dummy user for zod validation in response + orgId, + name: "No Access", + slug: "no-access", + description: "No access to any resources in the organization", + permissions: packRules(orgNoAccessPermissions.rules), + createdAt: new Date(), + updatedAt: new Date() + }; + } + default: { + const role = await orgRoleDAL.findOne({ id: roleId, orgId }); + if (!role) throw new BadRequestError({ message: "Role not found", name: "Get role" }); + return role; + } + } }; const updateRole = async ( diff --git a/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx b/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx index 4142569fd..6497f5ba6 100644 --- a/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx +++ b/frontend/src/views/Org/RolePage/components/RoleDetailsSection.tsx @@ -21,32 +21,35 @@ export const RoleDetailsSection = ({ roleId, handlePopUpOpen }: Props) => { const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; const { data } = useGetOrgRole(orgId, roleId); + const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? ""); return data ? (

Details

- - {(isAllowed) => { - return ( - - - handlePopUpOpen("role", { - roleId - }) - } - > - - - - ); - }} - + {isCustomRole && ( + + {(isAllowed) => { + return ( + + + handlePopUpOpen("role", { + roleId + }) + } + > + + + + ); + }} + + )}
diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx index 60c181d17..b27a95a22 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx @@ -3,7 +3,8 @@ import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form" import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Checkbox, IconButton, Select, SelectItem, Td, Tr } from "@app/components/v2"; +import { createNotification } from "@app/components/notifications"; +import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2"; import { useToggle } from "@app/hooks"; import { TFormSchema } from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils"; @@ -58,6 +59,7 @@ const getPermissionList = (option: string) => { }; type Props = { + isEditable: boolean; title: string; formName: keyof Omit, "workspace">; setValue: UseFormSetValue; @@ -76,7 +78,14 @@ enum Permission { // TODO: support for default roles -export const RolePermissionRow = ({ title, formName, handleSubmit, control, setValue }: Props) => { +export const RolePermissionRow = ({ + isEditable, + title, + formName, + handleSubmit, + control, + setValue +}: Props) => { const [isRowExpanded, setIsRowExpanded] = useToggle(); const [isCustom, setIsCustom] = useToggle(); @@ -159,14 +168,7 @@ export const RolePermissionRow = ({ title, formName, handleSubmit, control, setV onClick={() => setIsRowExpanded.toggle()} > - setIsRowExpanded.toggle()} - > - - + {title} @@ -175,6 +177,7 @@ export const RolePermissionRow = ({ title, formName, handleSubmit, control, setV className="w-40 bg-mineshaft-600" dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800" onValueChange={handlePermissionChange} + isDisabled={!isEditable} > No Access Read Only @@ -200,6 +203,13 @@ export const RolePermissionRow = ({ title, formName, handleSubmit, control, setV { + if (!isEditable) { + createNotification({ + type: "error", + text: "Failed to update default role" + }); + return; + } field.onChange(e); handleSubmit(); }} diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx index ac485b47b..0140d8eff 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx @@ -93,6 +93,8 @@ export const RolePermissionsTable = ({ roleId }: Props) => { } }; + const isCustomRole = !["admin", "member", "no-access"].includes(role?.slug ?? ""); + return (
@@ -114,6 +116,7 @@ export const RolePermissionsTable = ({ roleId }: Props) => { setValue={setValue} handleSubmit={handleSubmit(onSubmit)} key={`org-role-${roleId}-permission-${permission.formName}`} + isEditable={isCustomRole} /> ); })} From 29d76c1deb221c6b126dc09da63c9a43acd7ba2f Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 22 Jul 2024 19:02:03 +0700 Subject: [PATCH 08/10] Adjust OrgRoleTable --- .../OrgRoleTabSection/OrgRoleTable.tsx | 53 +++++----- frontend/src/views/Org/RolePage/RolePage.tsx | 99 ++++++++++--------- 2 files changed, 77 insertions(+), 75 deletions(-) diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx index 904aec264..da7829344 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx @@ -110,8 +110,6 @@ export const OrgRoleTable = () => { {(isAllowed) => ( { onClick={(e) => { e.stopPropagation(); router.push(`/org/${orgId}/roles/${id}`); - // onSelectRole(role); }} disabled={!isAllowed} > - Edit Role - - )} - - - {(isAllowed) => ( - { - e.stopPropagation(); - handlePopUpOpen("deleteRole", role); - }} - disabled={!isAllowed} - > - Delete Role + {`${isNonMutatable ? "View" : "Edit"} Role`} )} + {!isNonMutatable && ( + + {(isAllowed) => ( + { + e.stopPropagation(); + handlePopUpOpen("deleteRole", role); + }} + disabled={!isAllowed} + > + Delete Role + + )} + + )} diff --git a/frontend/src/views/Org/RolePage/RolePage.tsx b/frontend/src/views/Org/RolePage/RolePage.tsx index 0983d3506..c8e78a54e 100644 --- a/frontend/src/views/Org/RolePage/RolePage.tsx +++ b/frontend/src/views/Org/RolePage/RolePage.tsx @@ -17,7 +17,7 @@ import { } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { withPermission } from "@app/hoc"; -import { useDeleteOrgRole,useGetOrgRole } from "@app/hooks/api"; +import { useDeleteOrgRole, useGetOrgRole } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; import { RoleDetailsSection, RoleModal, RolePermissionsSection } from "./components"; @@ -64,6 +64,8 @@ export const RolePage = withPermission( } }; + const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? ""); + return (
{data && ( @@ -81,51 +83,56 @@ export const RolePage = withPermission(

{data.name}

- - -
- - - -
-
- - - {(isAllowed) => ( - { - handlePopUpOpen("role", { - roleId - }); - }} - disabled={!isAllowed} - > - Edit Role - - )} - - - {(isAllowed) => ( - { - handlePopUpOpen("deleteOrgRole"); - }} - disabled={!isAllowed} - > - Delete Role - - )} - - -
+ {isCustomRole && ( + + +
+ + + +
+
+ + + {(isAllowed) => ( + { + handlePopUpOpen("role", { + roleId + }); + }} + disabled={!isAllowed} + > + Edit Role + + )} + + + {(isAllowed) => ( + { + handlePopUpOpen("deleteOrgRole"); + }} + disabled={!isAllowed} + > + Delete Role + + )} + + +
+ )}
From b359f4278e5632fb5474828547d9b9ab0e20f354 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 22 Jul 2024 19:15:18 +0700 Subject: [PATCH 09/10] Fix type issues --- backend/src/ee/services/license/licence-fns.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/ee/services/license/licence-fns.ts b/backend/src/ee/services/license/licence-fns.ts index 8da8afd39..3e30276cb 100644 --- a/backend/src/ee/services/license/licence-fns.ts +++ b/backend/src/ee/services/license/licence-fns.ts @@ -32,7 +32,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ oidcSSO: false, scim: false, ldap: false, - groups: true, + groups: false, status: null, trial_end: null, has_used_trial: true, From d5c0abbc3b59991bb36b4481a1a1621f8ce366f8 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 23 Jul 2024 11:58:55 +0700 Subject: [PATCH 10/10] Opt for bulk save role permissions instead of save on each form change --- .../OrgRoleTabSection/OrgRoleTable.tsx | 1 - .../RolePermissionRow.tsx | 17 +- .../RolePermissionsSection.tsx | 152 +++++++++++++++++- .../RolePermissionsTable.tsx | 128 --------------- 4 files changed, 149 insertions(+), 149 deletions(-) delete mode 100644 frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx index da7829344..0fce9f06b 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx @@ -66,7 +66,6 @@ export const OrgRoleTable = () => { colorSchema="primary" type="submit" leftIcon={} - // onClick={() => onSelectRole()} onClick={() => { handlePopUpOpen("role"); }} diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx index b27a95a22..ca4902eb3 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionRow.tsx @@ -64,11 +64,8 @@ type Props = { formName: keyof Omit, "workspace">; setValue: UseFormSetValue; control: Control; - handleSubmit: () => void; }; -// permission categories - enum Permission { NoAccess = "no-access", ReadOnly = "read-only", @@ -76,16 +73,7 @@ enum Permission { Custom = "custom" } -// TODO: support for default roles - -export const RolePermissionRow = ({ - isEditable, - title, - formName, - handleSubmit, - control, - setValue -}: Props) => { +export const RolePermissionRow = ({ isEditable, title, formName, control, setValue }: Props) => { const [isRowExpanded, setIsRowExpanded] = useToggle(); const [isCustom, setIsCustom] = useToggle(); @@ -157,8 +145,6 @@ export const RolePermissionRow = ({ ); break; } - - handleSubmit(); }; return ( @@ -211,7 +197,6 @@ export const RolePermissionRow = ({ return; } field.onChange(e); - handleSubmit(); }} id={`permissions.${formName}.${action}`} > diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx index 2b7d43a7d..02422af41 100644 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -1,18 +1,162 @@ -import { RolePermissionsTable } from "./RolePermissionsTable"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; + +import { createNotification } from "@app/components/notifications"; +import { Button , Table, TableContainer, TBody, Th, THead, Tr } from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api"; +import { + formRolePermission2API, + formSchema, + rolePermission2Form, + TFormSchema +} from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils"; + +import { RolePermissionRow } from "./RolePermissionRow"; + +const SIMPLE_PERMISSION_OPTIONS = [ + { + title: "User management", + formName: "member" + }, + { + title: "Group management", + formName: "groups" + }, + { + title: "Machine identity management", + formName: "identity" + }, + { + title: "Billing & usage", + formName: "billing" + }, + { + title: "Role management", + formName: "role" + }, + { + title: "Incident Contacts", + formName: "incident-contact" + }, + { + title: "Organization profile", + formName: "settings" + }, + { + title: "Secret Scanning", + formName: "secret-scanning" + }, + { + title: "SSO", + formName: "sso" + }, + { + title: "LDAP", + formName: "ldap" + }, + { + title: "SCIM", + formName: "scim" + } +] as const; type Props = { roleId: string; }; export const RolePermissionsSection = ({ roleId }: Props) => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + + const { data: role } = useGetOrgRole(orgId, roleId); + + const { + setValue, + control, + handleSubmit, + formState: { isDirty, isSubmitting }, + reset + } = useForm({ + defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {}, + resolver: zodResolver(formSchema) + }); + + const { mutateAsync: updateRole } = useUpdateOrgRole(); + + const onSubmit = async (el: TFormSchema) => { + try { + await updateRole({ + orgId, + id: roleId, + ...el, + permissions: formRolePermission2API(el.permissions) + }); + createNotification({ type: "success", text: "Successfully updated role" }); + } catch (err) { + console.log(err); + createNotification({ type: "error", text: "Failed to update role" }); + } + }; + + const isCustomRole = !["admin", "member", "no-access"].includes(role?.slug ?? ""); + return ( -
+

Permissions

+ {isCustomRole && ( +
+ + +
+ )}
- + + + + + + + + + + {SIMPLE_PERMISSION_OPTIONS.map((permission) => { + return ( + + ); + })} + +
+ ResourcePermission
+
-
+ ); }; diff --git a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx b/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx deleted file mode 100644 index 0140d8eff..000000000 --- a/frontend/src/views/Org/RolePage/components/RolePermissionsSection/RolePermissionsTable.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; - -import { createNotification } from "@app/components/notifications"; -import { Table, TableContainer, TBody, Th, THead, Tr } from "@app/components/v2"; -import { useOrganization } from "@app/context"; -import { useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api"; -import { - formRolePermission2API, - formSchema, - rolePermission2Form, - TFormSchema -} from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils"; - -import { RolePermissionRow } from "./RolePermissionRow"; - -const SIMPLE_PERMISSION_OPTIONS = [ - { - title: "User management", - formName: "member" - }, - { - title: "Group management", - formName: "groups" - }, - { - title: "Machine identity management", - formName: "identity" - }, - { - title: "Billing & usage", - formName: "billing" - }, - { - title: "Role management", - formName: "role" - }, - { - title: "Incident Contacts", - formName: "incident-contact" - }, - { - title: "Organization profile", - formName: "settings" - }, - { - title: "Secret Scanning", - formName: "secret-scanning" - }, - { - title: "SSO", - formName: "sso" - }, - { - title: "LDAP", - formName: "ldap" - }, - { - title: "SCIM", - formName: "scim" - } -] as const; - -type Props = { - roleId: string; -}; - -export const RolePermissionsTable = ({ roleId }: Props) => { - const { currentOrg } = useOrganization(); - const orgId = currentOrg?.id || ""; - - const { data: role } = useGetOrgRole(orgId, roleId); - - const { setValue, control, handleSubmit } = useForm({ - defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {}, - resolver: zodResolver(formSchema) - }); - - const { mutateAsync: updateRole } = useUpdateOrgRole(); - - const onSubmit = async (el: TFormSchema) => { - try { - await updateRole({ - orgId, - id: roleId, - ...el, - permissions: formRolePermission2API(el.permissions) - }); - createNotification({ type: "success", text: "Successfully updated role" }); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update role" }); - } - }; - - const isCustomRole = !["admin", "member", "no-access"].includes(role?.slug ?? ""); - - return ( - -
- - - - - - - - - {SIMPLE_PERMISSION_OPTIONS.map((permission) => { - return ( - - ); - })} - -
- ResourcePermission
-
-
- ); -};