From bd860e6c5ac5a3d3f35124241e6fde6d434ea6cb Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 22 Jul 2024 12:41:26 +0700 Subject: [PATCH] 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} /> -