diff --git a/backend/src/ee/services/permission/permission-types.ts b/backend/src/ee/services/permission/permission-types.ts index 60fcbec85..8df85054d 100644 --- a/backend/src/ee/services/permission/permission-types.ts +++ b/backend/src/ee/services/permission/permission-types.ts @@ -11,8 +11,8 @@ export enum PermissionConditionOperators { } export const PermissionConditionSchema = { - [PermissionConditionOperators.$IN]: z.string().min(1).array(), - [PermissionConditionOperators.$ALL]: z.string().min(1).array(), + [PermissionConditionOperators.$IN]: z.string().trim().min(1).array(), + [PermissionConditionOperators.$ALL]: z.string().trim().min(1).array(), [PermissionConditionOperators.$REGEX]: z .string() .min(1) diff --git a/frontend/src/components/permissions/GlobPermissionInfo.tsx b/frontend/src/components/permissions/GlobPermissionInfo.tsx new file mode 100644 index 000000000..07ba42db3 --- /dev/null +++ b/frontend/src/components/permissions/GlobPermissionInfo.tsx @@ -0,0 +1,33 @@ +import { useState } from "react"; +import picomatch from "picomatch"; + +import { FormControl } from "../v2/FormControl"; +import { Input } from "../v2/Input"; + +export const GlobPermissionInfo = () => { + const [pattern, setPattern] = useState(""); + const [text, setText] = useState(""); + + return ( +
+
A glob pattern uses wildcards to match file names or paths.
+
+ + setPattern(e.target.value)} /> + +
+
+ + setText(e.target.value)} /> + +
+
+ ); +}; diff --git a/frontend/src/components/permissions/index.tsx b/frontend/src/components/permissions/index.tsx index 8d523c311..5103b0f73 100644 --- a/frontend/src/components/permissions/index.tsx +++ b/frontend/src/components/permissions/index.tsx @@ -1,3 +1,4 @@ +export { GlobPermissionInfo } from "./GlobPermissionInfo"; export { OrgPermissionCan } from "./OrgPermissionCan"; export { PermissionDeniedBanner } from "./PermissionDeniedBanner"; export { ProjectPermissionCan } from "./ProjectPermissionCan"; diff --git a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/GeneralPermissionConditions.tsx b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/GeneralPermissionConditions.tsx index da4dd72eb..989b7c19b 100644 --- a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/GeneralPermissionConditions.tsx +++ b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/GeneralPermissionConditions.tsx @@ -1,14 +1,26 @@ import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; +import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Button, FormControl, IconButton, Input, Select, SelectItem } from "@app/components/v2"; +import { + Button, + FormControl, + IconButton, + Input, + Select, + SelectItem, + Tooltip +} from "@app/components/v2"; import { PermissionConditionOperators, ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; import { TFormSchema } from "../ProjectRoleModifySection.utils"; +import { + getConditionOperatorHelperInfo, + renderOperatorSelectItems +} from "./PermissionConditionHelpers"; type Props = { position?: number; @@ -19,12 +31,6 @@ type Props = { | ProjectPermissionSub.SecretImports; }; -const getValueLabel = (type: string) => { - if (type === "environment") return "Environment slug"; - if (type === "secretPath") return "Folder path"; - return ""; -}; - export const GeneralPermissionConditions = ({ position = 0, isDisabled, type }: Props) => { const { control, @@ -40,7 +46,11 @@ export const GeneralPermissionConditions = ({ position = 0, isDisabled, type }:
{items.fields.map((el, index) => { - const lhs = watch(`permissions.${type}.${position}.conditions.${index}.lhs`); + const condition = watch(`permissions.secrets.${position}.conditions.${index}`) as { + lhs: string; + rhs: string; + operator: string; + }; return (
-
+
( field.onChange(e)} className="w-full" > - Equal - Not Equal - - Glob Match - - Contains + {renderOperatorSelectItems(condition.lhs)} )} /> +
+ + + +
- + )} /> diff --git a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/PermissionConditionHelpers.tsx b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/PermissionConditionHelpers.tsx new file mode 100644 index 000000000..21fad117a --- /dev/null +++ b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/PermissionConditionHelpers.tsx @@ -0,0 +1,32 @@ +import { GlobPermissionInfo } from "@app/components/permissions"; +import { SelectItem } from "@app/components/v2"; +import { PermissionConditionOperators } from "@app/context/ProjectPermissionContext/types"; + +export const getConditionOperatorHelperInfo = (type: PermissionConditionOperators) => { + switch (type) { + case PermissionConditionOperators.$EQ: + return "Value should equal specified value."; + case PermissionConditionOperators.$NEQ: + return "Value should not equal specified value."; + case PermissionConditionOperators.$IN: + return "List of comma-separated values that match a given value."; + case PermissionConditionOperators.$GLOB: + return ; + default: + return ""; + } +}; + +export const renderOperatorSelectItems = (type: string) => { + if (type === "secretTags") { + return Contains; + } + return ( + <> + Equal + Not Equal + Glob Match + Contains + + ); +}; diff --git a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/SecretPermissionConditions.tsx b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/SecretPermissionConditions.tsx index a6f94de90..e0e5aa471 100644 --- a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/SecretPermissionConditions.tsx +++ b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/components/SecretPermissionConditions.tsx @@ -1,23 +1,29 @@ import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; +import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Button, FormControl, IconButton, Input, Select, SelectItem } from "@app/components/v2"; +import { + Button, + FormControl, + IconButton, + Input, + Select, + SelectItem, + Tooltip +} from "@app/components/v2"; import { PermissionConditionOperators } from "@app/context/ProjectPermissionContext/types"; import { TFormSchema } from "../ProjectRoleModifySection.utils"; +import { + getConditionOperatorHelperInfo, + renderOperatorSelectItems +} from "./PermissionConditionHelpers"; type Props = { position?: number; isDisabled?: boolean; }; -const getValueLabel = (type: string) => { - if (type === "environment") return "Environment slug"; - if (type === "secretPath") return "Folder path"; - return ""; -}; - export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props) => { const { control, @@ -33,7 +39,11 @@ export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props)
{items.fields.map((el, index) => { - const lhs = watch(`permissions.secrets.${position}.conditions.${index}.lhs`); + const condition = watch(`permissions.secrets.${position}.conditions.${index}`) as { + lhs: string; + rhs: string; + operator: string; + }; return (
-
+
field.onChange(e)} className="w-full" > - Equal - Not Equal - - Glob Match - - Contains + {renderOperatorSelectItems(condition.lhs)} )} /> +
+ + + +
- + )} /> @@ -126,7 +142,6 @@ export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props) {errors?.permissions?.secrets?.[position]?.conditions?.message}
)} -
{}