From ffac24ce753d9c03f711510a9eaeb07617b05fde Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 24 Jun 2025 15:23:02 -0700 Subject: [PATCH] improvement: revise edit role page and access tree --- .../permissions/AccessTree/AccessTree.tsx | 102 ++++---- .../components/AccessTreeContext.tsx | 2 +- .../permissions/AccessTree/hooks/index.ts | 9 +- .../components/AccessTreeSecretPathInput.tsx | 2 +- .../permissions/AccessTree/nodes/RoleNode.tsx | 62 +---- .../AccessTree/utils/createRoleNode.ts | 14 +- .../AccessTree/utils/positionElements.ts | 10 - .../ProjectTemplateEditRoleForm.tsx | 24 +- .../src/components/v2/Checkbox/Checkbox.tsx | 9 +- .../context/ProjectPermissionContext/types.ts | 12 + ...rojectAdditionalPrivilegeModifySection.tsx | 28 +-- ...rojectAdditionalPrivilegeModifySection.tsx | 24 +- .../RoleDetailsBySlugPage.tsx | 51 ++-- .../components/AddPoliciesButton.tsx | 4 +- .../components/ConditionsFields.tsx | 206 +++++++++++++++++ .../DynamicSecretPermissionConditions.tsx | 191 ++------------- .../GeneralPermissionConditions.tsx | 176 +------------- .../components/GeneralPermissionPolicies.tsx | 217 +++++++++--------- ...IdentityManagementPermissionConditions.tsx | 171 +------------- .../components/PermissionConditionHelpers.tsx | 41 +++- .../components/PermissionEmptyState.tsx | 2 +- .../PkiSubscriberPermissionConditions.tsx | 170 +------------- .../PkiTemplatePermissionConditions.tsx | 170 +------------- .../components/RoleDetailsSection.tsx | 98 -------- .../components/RolePermissionsSection.tsx | 131 ++++++----- .../components/SecretPermissionConditions.tsx | 185 ++------------- .../SecretSyncPermissionConditions.tsx | 186 +-------------- .../SshHostPermissionConditions.tsx | 170 +------------- 28 files changed, 668 insertions(+), 1799 deletions(-) create mode 100644 frontend/src/pages/project/RoleDetailsBySlugPage/components/ConditionsFields.tsx delete mode 100644 frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleDetailsSection.tsx diff --git a/frontend/src/components/permissions/AccessTree/AccessTree.tsx b/frontend/src/components/permissions/AccessTree/AccessTree.tsx index 76e80d8b6..82ede1499 100644 --- a/frontend/src/components/permissions/AccessTree/AccessTree.tsx +++ b/frontend/src/components/permissions/AccessTree/AccessTree.tsx @@ -2,10 +2,9 @@ import { useCallback, useEffect, useState } from "react"; import { MongoAbility, MongoQuery } from "@casl/ability"; import { faAnglesUp, - faArrowUpRightFromSquare, - faDownLeftAndUpRightToCenter, faUpRightAndDownLeftFromCenter, - faWindowRestore + faWindowRestore, + faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { @@ -23,8 +22,8 @@ import { } from "@xyflow/react"; import { twMerge } from "tailwind-merge"; -import { Button, IconButton, Spinner, Tooltip } from "@app/components/v2"; -import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext"; +import { Button, IconButton, Select, SelectItem, Spinner, Tooltip } from "@app/components/v2"; +import { ProjectPermissionSet, ProjectPermissionSub } from "@app/context/ProjectPermissionContext"; import { AccessTreeSecretPathInput } from "./nodes/FolderNode/components/AccessTreeSecretPathInput"; import { ShowMoreButtonNode } from "./nodes/ShowMoreButtonNode"; @@ -36,15 +35,17 @@ import { ViewMode } from "./types"; export type AccessTreeProps = { permissions: MongoAbility; + subject: ProjectPermissionSub; + onClose: () => void; }; const EdgeTypes = { base: BasePermissionEdge }; const NodeTypes = { role: RoleNode, folder: FolderNode, showMoreButton: ShowMoreButtonNode }; -const AccessTreeContent = ({ permissions }: AccessTreeProps) => { +const AccessTreeContent = ({ permissions, subject, onClose }: AccessTreeProps) => { const [selectedPath, setSelectedPath] = useState("/"); - const accessTreeData = useAccessTree(permissions, selectedPath); + const accessTreeData = useAccessTree(permissions, selectedPath, subject); const { edges, nodes, isLoading, viewMode, setViewMode, environment } = accessTreeData; const [initialRender, setInitialRender] = useState(true); @@ -78,32 +79,32 @@ const AccessTreeContent = ({ permissions }: AccessTreeProps) => { useEffect(() => { setInitialRender(true); - }, [selectedPath, environment]); + }, [selectedPath, environment, subject, viewMode]); useEffect(() => { let timer: NodeJS.Timeout; if (initialRender) { timer = setTimeout(() => { - goToRootNode(); + fitView({ duration: 500 }); setInitialRender(false); - }, 500); + }, 50); } return () => clearTimeout(timer); - }, [nodes, edges, getViewport(), initialRender, goToRootNode]); + }, [nodes, edges, getViewport(), initialRender, fitView]); const handleToggleModalView = () => setViewMode((prev) => (prev === ViewMode.Modal ? ViewMode.Docked : ViewMode.Modal)); - const handleToggleUndockedView = () => - setViewMode((prev) => (prev === ViewMode.Undocked ? ViewMode.Docked : ViewMode.Undocked)); + const handleToggleView = () => + setViewMode((prev) => (prev === ViewMode.Modal ? ViewMode.Undocked : ViewMode.Modal)); - const undockButtonLabel = `${viewMode === ViewMode.Undocked ? "Dock" : "Undock"} View`; - const windowButtonLabel = `${viewMode === ViewMode.Modal ? "Dock" : "Expand"} View`; + const expandButtonLabel = viewMode === ViewMode.Modal ? "Anchor View" : "Expand View"; + const hideButtonLabel = "Hide Access Tree"; return (
{ type="submit" className="h-10 rounded-r-none bg-mineshaft-700" leftIcon={} - onClick={handleToggleUndockedView} + onClick={handleToggleView} > Undock @@ -176,48 +177,62 @@ const AccessTreeContent = ({ permissions }: AccessTreeProps) => { )} + {viewMode !== ViewMode.Undocked && ( + + + + + )} {viewMode !== ViewMode.Docked && ( - - {viewMode !== ViewMode.Undocked && ( - - )} - + + - + - + @@ -253,6 +268,9 @@ const AccessTreeContent = ({ permissions }: AccessTreeProps) => { }; export const AccessTree = (props: AccessTreeProps) => { + const { subject } = props; + if (!subject) return null; + return ( diff --git a/frontend/src/components/permissions/AccessTree/components/AccessTreeContext.tsx b/frontend/src/components/permissions/AccessTree/components/AccessTreeContext.tsx index 69c469540..cb7118b0c 100644 --- a/frontend/src/components/permissions/AccessTree/components/AccessTreeContext.tsx +++ b/frontend/src/components/permissions/AccessTree/components/AccessTreeContext.tsx @@ -29,7 +29,7 @@ export type AccessTreeForm = { metadata: { key: string; value: string }[] }; export const AccessTreeProvider: React.FC = ({ children }) => { const [secretName, setSecretName] = useState(""); const formMethods = useForm({ defaultValues: { metadata: [] } }); - const [viewMode, setViewMode] = useState(ViewMode.Docked); + const [viewMode, setViewMode] = useState(ViewMode.Modal); const value = useMemo( () => ({ diff --git a/frontend/src/components/permissions/AccessTree/hooks/index.ts b/frontend/src/components/permissions/AccessTree/hooks/index.ts index 64e9e04a3..717bdaf3a 100644 --- a/frontend/src/components/permissions/AccessTree/hooks/index.ts +++ b/frontend/src/components/permissions/AccessTree/hooks/index.ts @@ -33,7 +33,8 @@ type LevelFolderMap = Record< export const useAccessTree = ( permissions: MongoAbility, - searchPath: string + searchPath: string, + subject: ProjectPermissionSub ) => { const { currentWorkspace } = useWorkspace(); const { secretName, setSecretName, setViewMode, viewMode } = useAccessTreeContext(); @@ -41,7 +42,6 @@ export const useAccessTree = ( const metadata = useWatch({ control, name: "metadata" }); const [nodes, setNodes] = useNodesState([]); const [edges, setEdges] = useEdgesState([]); - const [subject, setSubject] = useState(ProjectPermissionSub.Secrets); const [environment, setEnvironment] = useState(currentWorkspace.environments[0]?.slug ?? ""); const { data: environmentsFolders, isPending } = useListProjectEnvironmentsFolders( currentWorkspace.id @@ -147,9 +147,7 @@ export const useAccessTree = ( const roleNode = createRoleNode({ subject, environment: slug, - environments: environmentsFolders, - onSubjectChange: setSubject, - onEnvironmentChange: setEnvironment + environments: environmentsFolders }); const actionRuleMap = getSubjectActionRuleMap(subject, permissions); @@ -280,7 +278,6 @@ export const useAccessTree = ( subject, environment, setEnvironment, - setSubject, isLoading: isPending, environments: currentWorkspace.environments, secretName, diff --git a/frontend/src/components/permissions/AccessTree/nodes/FolderNode/components/AccessTreeSecretPathInput.tsx b/frontend/src/components/permissions/AccessTree/nodes/FolderNode/components/AccessTreeSecretPathInput.tsx index 76a098ed3..d78a6f233 100644 --- a/frontend/src/components/permissions/AccessTree/nodes/FolderNode/components/AccessTreeSecretPathInput.tsx +++ b/frontend/src/components/permissions/AccessTree/nodes/FolderNode/components/AccessTreeSecretPathInput.tsx @@ -81,7 +81,7 @@ export const AccessTreeSecretPathInput = ({
) : ( - +
{ }; export const RoleNode = ({ - data: { subject, environment, onSubjectChange, onEnvironmentChange, environments } + data: { subject } }: NodeProps & { data: ReturnType["data"] & { onSubjectChange: Dispatch>; @@ -44,61 +43,10 @@ export const RoleNode = ({ className="pointer-events-none !cursor-pointer opacity-0" position={Position.Top} /> -
-
-
-
Subject
- -
- -
-
Environment
- -
+
+
+ {getSubjectIcon(subject)} + {formatLabel(subject)} Access
>; - onEnvironmentChange: (value: string) => void; }) => ({ id: `role-${subject}-${environment}`, position: { x: 0, y: 0 }, data: { subject, environment, - environments, - onSubjectChange, - onEnvironmentChange + environments }, type: PermissionNode.Role, height: 48, diff --git a/frontend/src/components/permissions/AccessTree/utils/positionElements.ts b/frontend/src/components/permissions/AccessTree/utils/positionElements.ts index c4daf6317..3f7c58f85 100644 --- a/frontend/src/components/permissions/AccessTree/utils/positionElements.ts +++ b/frontend/src/components/permissions/AccessTree/utils/positionElements.ts @@ -39,16 +39,6 @@ export const positionElements = (nodes: Node[], edges: Edge[]) => { const positionedNodes = nodes.map((node) => { const { x, y } = dagre.node(node.id); - if (node.type === "role") { - return { - ...node, - position: { - x: x - (node.width ? node.width / 2 : 0), - y: y - 150 - } - }; - } - return { ...node, position: { diff --git a/frontend/src/components/projects/ProjectSettings/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx b/frontend/src/components/projects/ProjectSettings/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx index a4bc3a73a..8e064e085 100644 --- a/frontend/src/components/projects/ProjectSettings/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx +++ b/frontend/src/components/projects/ProjectSettings/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx @@ -173,17 +173,19 @@ export const ProjectTemplateEditRoleForm = ({
Policies
- {(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]).map((subject) => ( - - {renderConditionalComponents(subject, isDisabled)} - - ))} +
+ {(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]).map((subject) => ( + + {renderConditionalComponents(subject, isDisabled)} + + ))} +
diff --git a/frontend/src/components/v2/Checkbox/Checkbox.tsx b/frontend/src/components/v2/Checkbox/Checkbox.tsx index 0cb2fd0e4..359cc1c95 100644 --- a/frontend/src/components/v2/Checkbox/Checkbox.tsx +++ b/frontend/src/components/v2/Checkbox/Checkbox.tsx @@ -40,9 +40,9 @@ export const Checkbox = ({
{isIndeterminate ? ( diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index a73dd8f7b..3f301942b 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -161,6 +161,18 @@ export type IdentityManagementSubjectFields = { identityId: string; }; +export type ConditionalProjectPermissionSubject = + | ProjectPermissionSub.SecretSyncs + | ProjectPermissionSub.Secrets + | ProjectPermissionSub.DynamicSecrets + | ProjectPermissionSub.Identity + | ProjectPermissionSub.SshHosts + | ProjectPermissionSub.PkiSubscribers + | ProjectPermissionSub.CertificateTemplates + | ProjectPermissionSub.SecretFolders + | ProjectPermissionSub.SecretImports + | ProjectPermissionSub.SecretRotation; + export const formatedConditionsOperatorNames: { [K in PermissionConditionOperators]: string } = { [PermissionConditionOperators.$EQ]: "equal to", [PermissionConditionOperators.$IN]: "in", diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx index 14eac860d..3ba76475f 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx @@ -352,19 +352,21 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({
Policies
{(isCreate || !isPending) && } - {(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]).map( - (permissionSubject) => ( - - {renderConditionalComponents(permissionSubject, isDisabled)} - - ) - )} +
+ {(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]).map( + (permissionSubject) => ( + + {renderConditionalComponents(permissionSubject, isDisabled)} + + ) + )} +
diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx index fb12f8381..92bdfc043 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx @@ -348,17 +348,19 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({
Policies
{(isCreate || !isPending) && } - {(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]).map((subject) => ( - - {renderConditionalComponents(subject, isDisabled)} - - ))} +
+ {(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]).map((subject) => ( + + {renderConditionalComponents(subject, isDisabled)} + + ))} +
diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx index 9a1336cb0..9579ab511 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx @@ -1,5 +1,7 @@ import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; +import { faCopy, faEdit, faEllipsisV, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useParams } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; @@ -12,19 +14,17 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, - PageHeader, - Tooltip + PageHeader } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { useDeleteProjectRole, useGetProjectRoleBySlug } from "@app/hooks/api"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { usePopUp } from "@app/hooks/usePopUp"; import { DuplicateProjectRoleModal } from "@app/pages/project/RoleDetailsBySlugPage/components/DuplicateProjectRoleModal"; +import { RolePermissionsSection } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection"; import { ProjectAccessControlTabs } from "@app/types/project"; -import { RoleDetailsSection } from "./components/RoleDetailsSection"; import { RoleModal } from "./components/RoleModal"; -import { RolePermissionsSection } from "./components/RolePermissionsSection"; const Page = () => { const navigate = useNavigate(); @@ -88,17 +88,29 @@ const Page = () => {
{data && (
- + +
+ {data.name} +

+ {data.slug} {data.description && `- ${data.description}`} +

+
+
+ } + > {isCustomRole && ( -
- - - -
+
- + { roleSlug }) } + icon={} disabled={!isAllowed} > Edit Role @@ -128,6 +141,7 @@ const Page = () => { className={twMerge( !isAllowed && "pointer-events-none cursor-not-allowed opacity-50" )} + icon={} onClick={() => { handlePopUpOpen("duplicateRole"); }} @@ -143,13 +157,9 @@ const Page = () => { > {(isAllowed) => ( } onClick={() => handlePopUpOpen("deleteRole")} - disabled={!isAllowed} + isDisabled={!isAllowed} > Delete Role @@ -159,12 +169,7 @@ const Page = () => {
)} -
-
- -
- -
+
)} diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx index f438f50e8..cedf07d05 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx @@ -24,7 +24,7 @@ export const AddPoliciesButton = ({ isDisabled }: Props) => { ] as const); return ( - <> +
); }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ConditionsFields.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ConditionsFields.tsx new file mode 100644 index 000000000..e548d3768 --- /dev/null +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ConditionsFields.tsx @@ -0,0 +1,206 @@ +import { Controller, useFieldArray, useFormContext } from "react-hook-form"; +import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { + Button, + FormControl, + IconButton, + Input, + Select, + SelectItem, + Tooltip +} from "@app/components/v2"; +import { + ConditionalProjectPermissionSubject, + PermissionConditionOperators +} from "@app/context/ProjectPermissionContext/types"; + +import { + getConditionOperatorHelperInfo, + renderOperatorSelectItems +} from "./PermissionConditionHelpers"; +import { TFormSchema } from "./ProjectRoleModifySection.utils"; + +export const ConditionsFields = ({ + isDisabled, + subject, + position, + selectOptions +}: { + isDisabled: boolean | undefined; + subject: ConditionalProjectPermissionSubject; + position: number; + selectOptions: [{ value: string; label: string }, ...{ value: string; label: string }[]]; +}) => { + const { + control, + watch, + setValue, + formState: { errors } + } = useFormContext(); + const items = useFieldArray({ + control, + name: `permissions.${subject}.${position}.conditions` + }); + + const conditionErrorMessage = + errors?.permissions?.[subject]?.[position]?.conditions?.message || + errors?.permissions?.[subject]?.[position]?.conditions?.root?.message; + + return ( +
+
+
+ Conditions + +

+ Conditions determine when a policy will be applied (always if no conditions are + present). +

+

+ All conditions must evaluate to true for the policy to take effect. +

+ + } + > + +
+
+ +
+
+ {Boolean(items.fields.length) && + items.fields.map((el, index) => { + const condition = + (watch(`permissions.${subject}.${position}.conditions.${index}`) as { + lhs: string; + rhs: string; + operator: string; + }) || {}; + return ( +
+
+ ( + + + + )} + /> +
+
+ ( + + + + )} + /> +
+ + + +
+
+
+ ( + + + + )} + /> +
+ items.remove(index)} + > + + +
+ ); + })} +
+ {conditionErrorMessage && ( +
+ + {conditionErrorMessage} +
+ )} +
+ ); +}; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/DynamicSecretPermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/DynamicSecretPermissionConditions.tsx index 0183a5d93..033b46eba 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/DynamicSecretPermissionConditions.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/DynamicSecretPermissionConditions.tsx @@ -1,26 +1,6 @@ -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; -import { - Button, - FormControl, - IconButton, - Input, - Select, - SelectItem, - Tooltip -} from "@app/components/v2"; -import { - PermissionConditionOperators, - ProjectPermissionSub -} from "@app/context/ProjectPermissionContext/types"; - -import { - getConditionOperatorHelperInfo, - renderOperatorSelectItems -} from "./PermissionConditionHelpers"; -import { TFormSchema } from "./ProjectRoleModifySection.utils"; +import { ConditionsFields } from "./ConditionsFields"; type Props = { position?: number; @@ -28,162 +8,17 @@ type Props = { }; export const DynamicSecretPermissionConditions = ({ position = 0, isDisabled }: Props) => { - const { - control, - watch, - setValue, - formState: { errors } - } = useFormContext(); - const items = useFieldArray({ - control, - name: `permissions.${ProjectPermissionSub.DynamicSecrets}.${position}.conditions` - }); - - const conditionErrorMessage = - errors?.permissions?.[ProjectPermissionSub.DynamicSecrets]?.[position]?.conditions?.message || - errors?.permissions?.[ProjectPermissionSub.DynamicSecrets]?.[position]?.conditions?.root - ?.message; - return ( -
-

Conditions

-

- Conditions determine when a policy will be applied (always if no conditions are present). -

-

- All conditions must evaluate to true for the policy to take effect. -

-
- {items.fields.map((el, index) => { - const condition = watch( - `permissions.${ProjectPermissionSub.DynamicSecrets}.${position}.conditions.${index}` - ) as { - lhs: string; - rhs: string; - operator: string; - }; - return ( -
-
- ( - - - - )} - /> -
-
- ( - - - - )} - /> -
- - - -
-
-
- ( - - - - )} - /> -
-
- items.remove(index)} - > - - -
-
- ); - })} -
- {conditionErrorMessage && ( -
- - {conditionErrorMessage} -
- )} -
- -
-
+ ); }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionConditions.tsx index bd705d1ff..e87e2f3a4 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionConditions.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionConditions.tsx @@ -1,180 +1,26 @@ -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; -import { - Button, - FormControl, - IconButton, - Input, - Select, - SelectItem, - Tooltip -} from "@app/components/v2"; -import { - PermissionConditionOperators, - ProjectPermissionSub -} from "@app/context/ProjectPermissionContext/types"; - -import { - getConditionOperatorHelperInfo, - renderOperatorSelectItems -} from "./PermissionConditionHelpers"; -import { TFormSchema } from "./ProjectRoleModifySection.utils"; +import { ConditionsFields } from "./ConditionsFields"; type Props = { position?: number; isDisabled?: boolean; type: - | ProjectPermissionSub.DynamicSecrets | ProjectPermissionSub.SecretFolders | ProjectPermissionSub.SecretImports | ProjectPermissionSub.SecretRotation; }; export const GeneralPermissionConditions = ({ position = 0, isDisabled, type }: Props) => { - const { - control, - watch, - formState: { errors } - } = useFormContext(); - const items = useFieldArray({ - control, - name: `permissions.${type}.${position}.conditions` - }); - return ( -
-

Conditions

-

- Conditions determine when a policy will be applied (always if no conditions are present). -

-

- All conditions must evaluate to true for the policy to take effect. -

-
- {items.fields.map((el, index) => { - const condition = - (watch(`permissions.${type}.${position}.conditions.${index}`) as { - lhs: string; - rhs: string; - operator: string; - }) || {}; - return ( -
-
- ( - - - - )} - /> -
-
- ( - - - - )} - /> -
- - - -
-
-
- ( - - - - )} - /> -
-
- items.remove(index)} - > - - -
-
- ); - })} -
- {errors?.permissions?.[type]?.[position]?.conditions?.message && ( -
- - {errors?.permissions?.[type]?.[position]?.conditions?.message} -
- )} -
{}
-
- -
-
+ ); }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx index 50b43e101..1fa7d9c52 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx @@ -3,6 +3,7 @@ import { Control, Controller, useFieldArray, useFormContext, useWatch } from "re import { faChevronDown, faChevronRight, + faDiagramProject, faGripVertical, faInfoCircle, faPlus, @@ -27,6 +28,7 @@ type Props = { actions: TProjectPermissionObject[T]["actions"]; children?: JSX.Element; isDisabled?: boolean; + onShowAccessTree?: (subject: ProjectPermissionSub) => void; }; type ActionProps = { @@ -71,7 +73,8 @@ export const GeneralPermissionPolicies = ) => { const { control, watch } = useFormContext(); const { fields, remove, insert, move } = useFieldArray({ @@ -89,7 +92,7 @@ export const GeneralPermissionPolicies = (null); const [dragOverItem, setDragOverItem] = useState(null); - if (!watchFields || !Array.isArray(watchFields) || watchFields.length === 0) return
; + if (!watchFields || !Array.isArray(watchFields) || watchFields.length === 0) return null; const handleDragStart = (_: React.DragEvent, index: number) => { setDraggedItem(index); @@ -121,9 +124,9 @@ export const GeneralPermissionPolicies = +
setIsOpen.toggle()} @@ -133,20 +136,50 @@ export const GeneralPermissionPolicies = -
- -
-
{title}
+ + +
{title}
{fields.length > 1 && (
- - {fields.length} rules + + {fields.length} Rules
)} + {onShowAccessTree && ( + + )} + {!isDisabled && isOpen && isConditionalSubjects(subject) && ( + + )}
{isOpen && ( -
+
{fields.map((el, rootIndex) => { let isFullReadAccessEnabled = false; @@ -154,78 +187,89 @@ export const GeneralPermissionPolicies = handleDragOver(e, rootIndex)} onDrop={handleDrop} > - {!isDisabled && ( - -
handleDragStart(e, rootIndex)} - onDragEnd={handleDragEnd} - className="absolute right-3 top-2 cursor-move rounded-md bg-mineshaft-700 p-2 text-gray-400 hover:text-gray-200" - > - -
-
- )} - -
- {isConditionalSubjects(subject) && ( + {isConditionalSubjects(subject) && ( +
-
Permission
-
- ( - - )} +
Permission
+ ( + + )} + /> + +

+ Whether to allow or forbid the selected actions when the following + conditions (if any) are met. +

+

Forbid rules must come after allow rules.

+ + } + > + -
-
- -

- Whether to allow or forbid the selected actions when the following - conditions (if any) are met. -

-

Forbid rules must come after allow rules.

- - } +
+ {!isDisabled && ( + + )} + {!isDisabled && ( + +
handleDragStart(e, rootIndex)} + onDragEnd={handleDragEnd} + className="cursor-move text-bunker-300 hover:text-bunker-200" + > + +
-
+ )}
- )} -
-
-
Actions
-
+
+ )} +
+
Actions
+
{actions.map(({ label, value }, index) => { if (typeof value !== "string") return undefined; @@ -255,41 +299,6 @@ export const GeneralPermissionPolicies = - {!isDisabled && isConditionalSubjects(subject) && ( - - )} - {!isDisabled && ( - - )}{" "} -
); })} diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/IdentityManagementPermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/IdentityManagementPermissionConditions.tsx index 7ce043fac..0e916f958 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/IdentityManagementPermissionConditions.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/IdentityManagementPermissionConditions.tsx @@ -1,23 +1,6 @@ -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; -import { - Button, - FormControl, - IconButton, - Input, - Select, - SelectItem, - Tooltip -} from "@app/components/v2"; -import { - PermissionConditionOperators, - ProjectPermissionSub -} from "@app/context/ProjectPermissionContext/types"; - -import { getConditionOperatorHelperInfo } from "./PermissionConditionHelpers"; -import { TFormSchema } from "./ProjectRoleModifySection.utils"; +import { ConditionsFields } from "./ConditionsFields"; type Props = { position?: number; @@ -25,150 +8,12 @@ type Props = { }; export const IdentityManagementPermissionConditions = ({ position = 0, isDisabled }: Props) => { - const { - control, - watch, - formState: { errors } - } = useFormContext(); - const permissionSubject = ProjectPermissionSub.Identity; - const items = useFieldArray({ - control, - name: `permissions.${permissionSubject}.${position}.conditions` - }); - return ( -
-

Conditions

-

- Conditions determine when a policy will be applied (always if no conditions are present). -

-

- All conditions must evaluate to true for the policy to take effect. -

-
- {items.fields.map((el, index) => { - const condition = - (watch(`permissions.${permissionSubject}.${position}.conditions.${index}`) as { - lhs: string; - rhs: string; - operator: string; - }) || {}; - return ( -
-
- ( - - - - )} - /> -
-
- ( - - - - )} - /> -
- - - -
-
-
- ( - - - - )} - /> -
-
- items.remove(index)} - > - - -
-
- ); - })} -
- {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message && ( -
- - {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message} -
- )} -
{}
-
- -
-
+ ); }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionConditionHelpers.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionConditionHelpers.tsx index 9120f0364..a25ee181d 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionConditionHelpers.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionConditionHelpers.tsx @@ -17,17 +17,36 @@ export const getConditionOperatorHelperInfo = (type: PermissionConditionOperator } }; +// scott: we may need to pass the subject in the future to further refine returned items export const renderOperatorSelectItems = (type: string) => { - if (type === "secretTags") { - return Contains; + switch (type) { + case "secretTags": + return Contains; + case "identityId": + return ( + <> + Equal + Not Equal + In + + ); + case "hostname": + case "name": + return ( + <> + Equals + Glob + In + + ); + default: + return ( + <> + Equal + Not Equal + Glob Match + In + + ); } - - return ( - <> - Equal - Not Equal - Glob Match - In - - ); }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState.tsx index 9a1bdab49..03699faab 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState.tsx @@ -12,7 +12,7 @@ export const PermissionEmptyState = () => { ([key, value]) => key && value?.length > 0 ); - if (isNotEmptyPermissions) return
; + if (isNotEmptyPermissions) return null; return ; }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiSubscriberPermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiSubscriberPermissionConditions.tsx index 58840fe2f..24030b15f 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiSubscriberPermissionConditions.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiSubscriberPermissionConditions.tsx @@ -1,23 +1,6 @@ -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; -import { - Button, - FormControl, - IconButton, - Input, - Select, - SelectItem, - Tooltip -} from "@app/components/v2"; -import { - PermissionConditionOperators, - ProjectPermissionSub -} from "@app/context/ProjectPermissionContext/types"; - -import { getConditionOperatorHelperInfo } from "./PermissionConditionHelpers"; -import { TFormSchema } from "./ProjectRoleModifySection.utils"; +import { ConditionsFields } from "./ConditionsFields"; type Props = { position?: number; @@ -25,149 +8,12 @@ type Props = { }; export const PkiSubscriberPermissionConditions = ({ position = 0, isDisabled }: Props) => { - const { - control, - watch, - formState: { errors } - } = useFormContext(); - - const permissionSubject = ProjectPermissionSub.PkiSubscribers; - const items = useFieldArray({ - control, - name: `permissions.${permissionSubject}.${position}.conditions` - }); - return ( -
-

Conditions

-

- Conditions determine when a policy will be applied (always if no conditions are present). -

-

- All conditions must evaluate to true for the policy to take effect. -

-
- {items.fields.map((el, index) => { - const condition = - (watch(`permissions.${permissionSubject}.${position}.conditions.${index}`) as { - lhs: string; - rhs: string; - operator: string; - }) || {}; - - return ( -
-
- ( - - - - )} - /> -
-
- ( - - - - )} - /> - - - -
-
- ( - - - - )} - /> -
-
- items.remove(index)} - > - - -
-
- ); - })} -
- {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message && ( -
- - {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message} -
- )} -
- -
-
+ ); }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiTemplatePermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiTemplatePermissionConditions.tsx index 2ca4e2d14..1bb2d1948 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiTemplatePermissionConditions.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiTemplatePermissionConditions.tsx @@ -1,23 +1,6 @@ -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; -import { - Button, - FormControl, - IconButton, - Input, - Select, - SelectItem, - Tooltip -} from "@app/components/v2"; -import { - PermissionConditionOperators, - ProjectPermissionSub -} from "@app/context/ProjectPermissionContext/types"; - -import { getConditionOperatorHelperInfo } from "./PermissionConditionHelpers"; -import { TFormSchema } from "./ProjectRoleModifySection.utils"; +import { ConditionsFields } from "./ConditionsFields"; type Props = { position?: number; @@ -25,149 +8,12 @@ type Props = { }; export const PkiTemplatePermissionConditions = ({ position = 0, isDisabled }: Props) => { - const { - control, - watch, - formState: { errors } - } = useFormContext(); - - const permissionSubject = ProjectPermissionSub.CertificateTemplates; - const items = useFieldArray({ - control, - name: `permissions.${permissionSubject}.${position}.conditions` - }); - return ( -
-

Conditions

-

- Conditions determine when a policy will be applied (always if no conditions are present). -

-

- All conditions must evaluate to true for the policy to take effect. -

-
- {items.fields.map((el, index) => { - const condition = - (watch(`permissions.${permissionSubject}.${position}.conditions.${index}`) as { - lhs: string; - rhs: string; - operator: string; - }) || {}; - - return ( -
-
- ( - - - - )} - /> -
-
- ( - - - - )} - /> - - - -
-
- ( - - - - )} - /> -
-
- items.remove(index)} - > - - -
-
- ); - })} -
- {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message && ( -
- - {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message} -
- )} -
- -
-
+ ); }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleDetailsSection.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleDetailsSection.tsx deleted file mode 100644 index 48816709d..000000000 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleDetailsSection.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { faCheck, faCopy, faPencil } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -import { ProjectPermissionCan } from "@app/components/permissions"; -import { IconButton, Tooltip } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; -import { useTimedReset } from "@app/hooks"; -import { useGetProjectRoleBySlug } from "@app/hooks/api"; -import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; -import { UsePopUpState } from "@app/hooks/usePopUp"; - -type Props = { - roleSlug: string; - handlePopUpOpen: (popUpName: keyof UsePopUpState<["role"]>, data?: object) => void; -}; - -export const RoleDetailsSection = ({ roleSlug, handlePopUpOpen }: Props) => { - const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset({ - initialState: "Copy ID to clipboard" - }); - - const { currentWorkspace } = useWorkspace(); - const { data } = useGetProjectRoleBySlug(currentWorkspace?.id ?? "", roleSlug as string); - - const isCustomRole = !Object.values(ProjectMembershipRole).includes( - (data?.slug ?? "") as ProjectMembershipRole - ); - - return data ? ( -
-
-

Project Role Details

- {isCustomRole && ( - - {(isAllowed) => { - return ( - - { - handlePopUpOpen("role", { - roleSlug - }); - }} - > - - - - ); - }} - - )} -
-
-
-

Role ID

-
-

{data.id}

-
- - { - navigator.clipboard.writeText(data.id); - setCopyTextId("Copied"); - }} - > - - - -
-
-
-
-

Name

-

{data.name}

-
-
-

Slug

-

{data.slug}

-
-
-

Description

-

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

-
-
-
- ) : ( -
- ); -}; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx index 09f3920fd..1bb552b7e 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { MongoAbility, MongoQuery, RawRuleOf } from "@casl/ability"; import { faSave } from "@fortawesome/free-solid-svg-icons"; @@ -88,6 +88,8 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { roleSlug as string ); + const [showAccessTree, setShowAccessTree] = useState(null); + const form = useForm({ values: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : undefined, resolver: zodResolver(projectRoleFormSchema) @@ -133,71 +135,90 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { [JSON.stringify(permissions)] ); + const isSecretManagerProject = currentWorkspace.type === ProjectType.SecretManager; + return (
- {currentWorkspace.type === ProjectType.SecretManager && ( - - )}
-
-

Policies

-
- {isCustomRole && ( - <> - {isDirty && ( - - )} -
- - -
- - )} +
+
+

Policies

+

+ Configure granular access policies +

-
-
- {!isPending && } - {(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]) - .filter((subject) => !EXCLUDED_PERMISSION_SUBS.includes(subject)) - .filter((subject) => ProjectTypePermissionSubjects[currentWorkspace.type][subject]) - .map((subject) => ( - + {isDirty && ( + + )} + +
+ +
+
+ )} +
+
+
+ {!isPending && } + {(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]) + .filter((subject) => !EXCLUDED_PERMISSION_SUBS.includes(subject)) + .filter((subject) => ProjectTypePermissionSubjects[currentWorkspace.type][subject]) + .map((subject) => ( + + {renderConditionalComponents(subject, isDisabled)} + + ))} +
+ {isSecretManagerProject && showAccessTree && ( + setShowAccessTree(null)} + /> + )}
); }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/SecretPermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/SecretPermissionConditions.tsx index bfbe20af7..9978b3e45 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/SecretPermissionConditions.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/SecretPermissionConditions.tsx @@ -1,23 +1,6 @@ -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; -import { - Button, - FormControl, - IconButton, - Input, - Select, - SelectItem, - Tooltip -} from "@app/components/v2"; -import { PermissionConditionOperators } from "@app/context/ProjectPermissionContext/types"; - -import { - getConditionOperatorHelperInfo, - renderOperatorSelectItems -} from "./PermissionConditionHelpers"; -import { TFormSchema } from "./ProjectRoleModifySection.utils"; +import { ConditionsFields } from "./ConditionsFields"; type Props = { position?: number; @@ -25,159 +8,17 @@ type Props = { }; export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props) => { - const { - control, - watch, - setValue, - formState: { errors } - } = useFormContext(); - const items = useFieldArray({ - control, - name: `permissions.secrets.${position}.conditions` - }); - - const conditionErrorMessage = - errors?.permissions?.secrets?.[position]?.conditions?.message || - errors?.permissions?.secrets?.[position]?.conditions?.root?.message; - return ( -
-

Conditions

-

- Conditions determine when a policy will be applied (always if no conditions are present). -

-

- All conditions must evaluate to true for the policy to take effect. -

-
- {items.fields.map((el, index) => { - const condition = watch(`permissions.secrets.${position}.conditions.${index}`) as { - lhs: string; - rhs: string; - operator: string; - }; - return ( -
-
- ( - - - - )} - /> -
-
- ( - - - - )} - /> -
- - - -
-
-
- ( - - - - )} - /> -
-
- items.remove(index)} - > - - -
-
- ); - })} -
- {conditionErrorMessage && ( -
- - {conditionErrorMessage} -
- )} -
- -
-
+ ); }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/SecretSyncPermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/SecretSyncPermissionConditions.tsx index b5f8f5c91..b168f2076 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/SecretSyncPermissionConditions.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/SecretSyncPermissionConditions.tsx @@ -1,26 +1,6 @@ -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; -import { - Button, - FormControl, - IconButton, - Input, - Select, - SelectItem, - Tooltip -} from "@app/components/v2"; -import { - PermissionConditionOperators, - ProjectPermissionSub -} from "@app/context/ProjectPermissionContext/types"; - -import { - getConditionOperatorHelperInfo, - renderOperatorSelectItems -} from "./PermissionConditionHelpers"; -import { TFormSchema } from "./ProjectRoleModifySection.utils"; +import { ConditionsFields } from "./ConditionsFields"; type Props = { position?: number; @@ -28,159 +8,15 @@ type Props = { }; export const SecretSyncPermissionConditions = ({ position = 0, isDisabled }: Props) => { - const { - control, - watch, - setValue, - formState: { errors } - } = useFormContext(); - const items = useFieldArray({ - control, - name: `permissions.${ProjectPermissionSub.SecretSyncs}.${position}.conditions` - }); - - const conditionErrorMessage = - errors?.permissions?.[ProjectPermissionSub.SecretSyncs]?.[position]?.conditions?.message || - errors?.permissions?.[ProjectPermissionSub.SecretSyncs]?.[position]?.conditions?.root?.message; - return ( -
-

Conditions

-

- Conditions determine when a policy will be applied (always if no conditions are present). -

-

- All conditions must evaluate to true for the policy to take effect. -

-
- {items.fields.map((el, index) => { - const condition = watch( - `permissions.${ProjectPermissionSub.SecretSyncs}.${position}.conditions.${index}` - ) as { - lhs: string; - rhs: string; - operator: string; - }; - return ( -
-
- ( - - - - )} - /> -
-
- ( - - - - )} - /> -
- - - -
-
-
- ( - - - - )} - /> -
-
- items.remove(index)} - > - - -
-
- ); - })} -
- {conditionErrorMessage && ( -
- - {conditionErrorMessage} -
- )} -
- -
-
+ ); }; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/SshHostPermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/SshHostPermissionConditions.tsx index 85cba0d94..1614438ec 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/SshHostPermissionConditions.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/SshHostPermissionConditions.tsx @@ -1,23 +1,6 @@ -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; -import { - Button, - FormControl, - IconButton, - Input, - Select, - SelectItem, - Tooltip -} from "@app/components/v2"; -import { - PermissionConditionOperators, - ProjectPermissionSub -} from "@app/context/ProjectPermissionContext/types"; - -import { getConditionOperatorHelperInfo } from "./PermissionConditionHelpers"; -import { TFormSchema } from "./ProjectRoleModifySection.utils"; +import { ConditionsFields } from "./ConditionsFields"; type Props = { position?: number; @@ -25,149 +8,12 @@ type Props = { }; export const SshHostPermissionConditions = ({ position = 0, isDisabled }: Props) => { - const { - control, - watch, - formState: { errors } - } = useFormContext(); - - const permissionSubject = ProjectPermissionSub.SshHosts; - const items = useFieldArray({ - control, - name: `permissions.${permissionSubject}.${position}.conditions` - }); - return ( -
-

Conditions

-

- Conditions determine when a policy will be applied (always if no conditions are present). -

-

- All conditions must evaluate to true for the policy to take effect. -

-
- {items.fields.map((el, index) => { - const condition = - (watch(`permissions.${permissionSubject}.${position}.conditions.${index}`) as { - lhs: string; - rhs: string; - operator: string; - }) || {}; - - return ( -
-
- ( - - - - )} - /> -
-
- ( - - - - )} - /> - - - -
-
- ( - - field.onChange(e.target.value.trim())} /> - - )} - /> -
-
- items.remove(index)} - > - - -
-
- ); - })} -
- {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message && ( -
- - {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message} -
- )} -
- -
-
+ ); };