mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 21:26:40 +00:00
feat(rbac): made org role section permission into one component and fixed some small issues
This commit is contained in:
@@ -658,7 +658,7 @@ export const DashboardPage = withProjectPermission(
|
|||||||
}, [selectedEnv?.slug, (popUp?.deleteFolder?.data as TDeleteFolderForm)?.id]);
|
}, [selectedEnv?.slug, (popUp?.deleteFolder?.data as TDeleteFolderForm)?.id]);
|
||||||
|
|
||||||
// SECRET IMPORT SECTION
|
// SECRET IMPORT SECTION
|
||||||
const handleSecretImportCreate = async (env: string, secretPath: string) => {
|
const handleSecretImportCreate = async (env: string, secPath: string) => {
|
||||||
try {
|
try {
|
||||||
await createSecretImport({
|
await createSecretImport({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -666,7 +666,7 @@ export const DashboardPage = withProjectPermission(
|
|||||||
folderId,
|
folderId,
|
||||||
secretImport: {
|
secretImport: {
|
||||||
environment: env,
|
environment: env,
|
||||||
secretPath
|
secretPath: secPath
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
createNotification({
|
createNotification({
|
||||||
|
|||||||
-149
@@ -1,149 +0,0 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
|
||||||
import { faContactCard } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
|
|
||||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isNonEditable?: boolean;
|
|
||||||
setValue: UseFormSetValue<TFormSchema>;
|
|
||||||
control: Control<TFormSchema>;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Permission {
|
|
||||||
NoAccess = "no-access",
|
|
||||||
ReadOnly = "read-only",
|
|
||||||
FullAccess = "full-acess",
|
|
||||||
Custom = "custom"
|
|
||||||
}
|
|
||||||
|
|
||||||
const PERMISSIONS = [
|
|
||||||
{ action: "read", label: "Read" },
|
|
||||||
{ action: "create", label: "Create" },
|
|
||||||
{ action: "edit", label: "Update" },
|
|
||||||
{ action: "delete", label: "Remove" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const IncidentContactPermission = ({ isNonEditable, setValue, control }: Props) => {
|
|
||||||
const rule = useWatch({
|
|
||||||
control,
|
|
||||||
name: "permissions.incident-contact"
|
|
||||||
});
|
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
|
||||||
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]);
|
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
|
||||||
if (val === Permission.Custom) setIsCustom.on();
|
|
||||||
else setIsCustom.off();
|
|
||||||
|
|
||||||
switch (val) {
|
|
||||||
case Permission.NoAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.incident-contact",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.FullAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.incident-contact",
|
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.ReadOnly:
|
|
||||||
setValue(
|
|
||||||
"permissions.incident-contact",
|
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setValue(
|
|
||||||
"permissions.incident-contact",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
|
||||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div>
|
|
||||||
<FontAwesomeIcon icon={faContactCard} className="text-4xl" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-grow flex flex-col">
|
|
||||||
<div className="font-medium mb-1 text-lg">Incident Contact</div>
|
|
||||||
<div className="text-xs font-light">Incident Contacts management control</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
defaultValue={Permission.NoAccess}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
value={selectedPermissionCategory}
|
|
||||||
onValueChange={handlePermissionChange}
|
|
||||||
>
|
|
||||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
|
||||||
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<motion.div
|
|
||||||
initial={false}
|
|
||||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
|
||||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
|
||||||
>
|
|
||||||
{isCustom &&
|
|
||||||
PERMISSIONS.map(({ action, label }) => (
|
|
||||||
<Controller
|
|
||||||
name={`permissions.incident-contact.${action}`}
|
|
||||||
key={`permissions.incident-contact.${action}`}
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
isChecked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
id={`permissions.incident-contact.${action}`}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
-149
@@ -1,149 +0,0 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
|
||||||
import { faUsers } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
|
|
||||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isNonEditable?: boolean;
|
|
||||||
setValue: UseFormSetValue<TFormSchema>;
|
|
||||||
control: Control<TFormSchema>;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Permission {
|
|
||||||
NoAccess = "no-access",
|
|
||||||
ReadOnly = "read-only",
|
|
||||||
FullAccess = "full-acess",
|
|
||||||
Custom = "custom"
|
|
||||||
}
|
|
||||||
|
|
||||||
const PERMISSIONS = [
|
|
||||||
{ action: "read", label: "Read" },
|
|
||||||
{ action: "create", label: "Invite" },
|
|
||||||
{ action: "edit", label: "Update" },
|
|
||||||
{ action: "delete", label: "Remove" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const MemberPermission = ({ isNonEditable, setValue, control }: Props) => {
|
|
||||||
const rule = useWatch({
|
|
||||||
control,
|
|
||||||
name: "permissions.member"
|
|
||||||
});
|
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
|
||||||
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]);
|
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
|
||||||
if (val === Permission.Custom) setIsCustom.on();
|
|
||||||
else setIsCustom.off();
|
|
||||||
|
|
||||||
switch (val) {
|
|
||||||
case Permission.NoAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.member",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.FullAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.member",
|
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.ReadOnly:
|
|
||||||
setValue(
|
|
||||||
"permissions.member",
|
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setValue(
|
|
||||||
"permissions.member",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
|
||||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div>
|
|
||||||
<FontAwesomeIcon icon={faUsers} className="text-4xl" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-grow flex flex-col">
|
|
||||||
<div className="font-medium mb-1 text-lg">Members</div>
|
|
||||||
<div className="text-xs font-light">Project member management control</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
defaultValue={Permission.NoAccess}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
value={selectedPermissionCategory}
|
|
||||||
onValueChange={handlePermissionChange}
|
|
||||||
>
|
|
||||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
|
||||||
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<motion.div
|
|
||||||
initial={false}
|
|
||||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
|
||||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
|
||||||
>
|
|
||||||
{isCustom &&
|
|
||||||
PERMISSIONS.map(({ action, label }) => (
|
|
||||||
<Controller
|
|
||||||
name={`permissions.member.${action}`}
|
|
||||||
key={`permissions.member.${action}`}
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
isChecked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
id={`permissions.member.${action}`}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+70
-46
@@ -1,6 +1,15 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { faArrowLeft, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faArrowLeft,
|
||||||
|
faCog,
|
||||||
|
faContactCard,
|
||||||
|
faMagnifyingGlass,
|
||||||
|
faMoneyBill,
|
||||||
|
faSignIn,
|
||||||
|
faUserCog,
|
||||||
|
faUsers
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
@@ -10,19 +19,13 @@ import { useOrganization } from "@app/context";
|
|||||||
import { useCreateRole, useUpdateRole } from "@app/hooks/api";
|
import { useCreateRole, useUpdateRole } from "@app/hooks/api";
|
||||||
import { TRole } from "@app/hooks/api/roles/types";
|
import { TRole } from "@app/hooks/api/roles/types";
|
||||||
|
|
||||||
import { BillingPermission } from "./BillingPermission";
|
|
||||||
import { IncidentContactPermission } from "./IncidentContactPermission";
|
|
||||||
import { MemberPermission } from "./MemberPermission";
|
|
||||||
import {
|
import {
|
||||||
formRolePermission2API,
|
formRolePermission2API,
|
||||||
formSchema,
|
formSchema,
|
||||||
rolePermission2Form,
|
rolePermission2Form,
|
||||||
TFormSchema
|
TFormSchema
|
||||||
} from "./OrgRoleModifySection.utils";
|
} from "./OrgRoleModifySection.utils";
|
||||||
import { RolePermission } from "./RolePermission";
|
import { SimpleLevelPermissionOption } from "./SimpleLevelPermissionOptions";
|
||||||
import { SecretScannigPermission } from "./SecretScanningPermission";
|
|
||||||
import { SettingsPermission } from "./SettingsPermission";
|
|
||||||
import { SsoPermission } from "./SsoPermission";
|
|
||||||
import { WorkspacePermission } from "./WorkspacePermission";
|
import { WorkspacePermission } from "./WorkspacePermission";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -30,6 +33,51 @@ type Props = {
|
|||||||
onGoBack: VoidFunction;
|
onGoBack: VoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SIMPLE_PERMISSION_OPTIONS = [
|
||||||
|
{
|
||||||
|
title: "Members",
|
||||||
|
subtitle: "Project member management control",
|
||||||
|
icon: faUsers,
|
||||||
|
formName: "member"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Billing",
|
||||||
|
subtitle: "Billing management control",
|
||||||
|
icon: faMoneyBill,
|
||||||
|
formName: "billing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Role",
|
||||||
|
subtitle: "Org role management control",
|
||||||
|
icon: faUserCog,
|
||||||
|
formName: "role"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Incident Contacts",
|
||||||
|
subtitle: "Incident contacts management control",
|
||||||
|
icon: faContactCard,
|
||||||
|
formName: "incident-contact"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Settings",
|
||||||
|
subtitle: "Settings management control",
|
||||||
|
icon: faCog,
|
||||||
|
formName: "settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Secret Scanning",
|
||||||
|
subtitle: "Secret scanning management control",
|
||||||
|
icon: faMagnifyingGlass,
|
||||||
|
formName: "secret-scanning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "SSO",
|
||||||
|
subtitle: "SSO management control",
|
||||||
|
icon: faSignIn,
|
||||||
|
formName: "sso"
|
||||||
|
}
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
|
export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||||
const [searchPermission, setSearchPermission] = useState("");
|
const [searchPermission, setSearchPermission] = useState("");
|
||||||
|
|
||||||
@@ -148,50 +196,26 @@ export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="">
|
||||||
<WorkspacePermission
|
<WorkspacePermission
|
||||||
isNonEditable={isNonEditable}
|
isNonEditable={isNonEditable}
|
||||||
control={control}
|
control={control}
|
||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-4">
|
{SIMPLE_PERMISSION_OPTIONS.map(({ title, subtitle, icon, formName }) => (
|
||||||
<MemberPermission isNonEditable={isNonEditable} control={control} setValue={setValue} />
|
<div key={`permission-${title}`}>
|
||||||
</div>
|
<SimpleLevelPermissionOption
|
||||||
<div className="flex flex-col space-y-4">
|
isNonEditable={isNonEditable}
|
||||||
<BillingPermission
|
control={control}
|
||||||
isNonEditable={isNonEditable}
|
setValue={setValue}
|
||||||
control={control}
|
icon={icon}
|
||||||
setValue={setValue}
|
title={title}
|
||||||
/>
|
subtitle={subtitle}
|
||||||
</div>
|
formName={formName}
|
||||||
<div className="flex flex-col space-y-4">
|
/>
|
||||||
<RolePermission isNonEditable={isNonEditable} control={control} setValue={setValue} />
|
</div>
|
||||||
</div>
|
))}
|
||||||
<div className="flex flex-col space-y-4">
|
|
||||||
<IncidentContactPermission
|
|
||||||
isNonEditable={isNonEditable}
|
|
||||||
control={control}
|
|
||||||
setValue={setValue}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col space-y-4">
|
|
||||||
<SettingsPermission
|
|
||||||
isNonEditable={isNonEditable}
|
|
||||||
control={control}
|
|
||||||
setValue={setValue}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col space-y-4">
|
|
||||||
<SecretScannigPermission
|
|
||||||
isNonEditable={isNonEditable}
|
|
||||||
control={control}
|
|
||||||
setValue={setValue}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col space-y-4">
|
|
||||||
<SsoPermission isNonEditable={isNonEditable} control={control} setValue={setValue} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4 mt-12">
|
<div className="flex items-center space-x-4 mt-12">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
+20
-14
@@ -3,22 +3,26 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { TPermission } from "@app/hooks/api/roles/types";
|
import { TPermission } from "@app/hooks/api/roles/types";
|
||||||
|
|
||||||
const generalPermissionSchema = z.object({
|
const generalPermissionSchema = z
|
||||||
read: z.boolean().optional(),
|
.object({
|
||||||
edit: z.boolean().optional(),
|
read: z.boolean().optional(),
|
||||||
delete: z.boolean().optional(),
|
edit: z.boolean().optional(),
|
||||||
create: z.boolean().optional()
|
delete: z.boolean().optional(),
|
||||||
});
|
create: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.optional();
|
||||||
|
|
||||||
export const formSchema = z.object({
|
export const formSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
permissions: z.object({
|
permissions: z.object({
|
||||||
workspace: z.object({
|
workspace: z
|
||||||
read: z.boolean().optional(),
|
.object({
|
||||||
create: z.boolean().optional()
|
read: z.boolean().optional(),
|
||||||
}),
|
create: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
member: generalPermissionSchema,
|
member: generalPermissionSchema,
|
||||||
role: generalPermissionSchema,
|
role: generalPermissionSchema,
|
||||||
settings: generalPermissionSchema,
|
settings: generalPermissionSchema,
|
||||||
@@ -47,8 +51,10 @@ export const rolePermission2Form = (permissions: TPermission[] = []) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
permissions.forEach((permission) => {
|
permissions.forEach((permission) => {
|
||||||
// akhilmhdh: this is typecast as workspace key else i would need an if loop with same condition on both side
|
// akhilmhdh: this is typecast as something other than workspace key else i would need an if loop with same condition on both side
|
||||||
formVal[permission.subject][permission.action as keyof typeof formVal.workspace] = true;
|
const key = permission.subject as keyof TFormSchema["permissions"];
|
||||||
|
(formVal[key] as Exclude<TFormSchema["permissions"]["member"], undefined>)[permission.action] =
|
||||||
|
true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return formVal;
|
return formVal;
|
||||||
@@ -59,13 +65,13 @@ export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
|
|||||||
(Object.keys(formVal) as Array<keyof typeof formVal>).forEach((rule) => {
|
(Object.keys(formVal) as Array<keyof typeof formVal>).forEach((rule) => {
|
||||||
// all these type annotations are due to Object.keys of ts cannot infer and put it just a string[]
|
// all these type annotations are due to Object.keys of ts cannot infer and put it just a string[]
|
||||||
// quite annoying i know
|
// quite annoying i know
|
||||||
const actions = Object.keys(formVal[rule]) as Array<
|
const actions = Object.keys(formVal[rule] || {}) as Array<
|
||||||
keyof z.infer<typeof generalPermissionSchema>
|
keyof z.infer<typeof generalPermissionSchema>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
actions.forEach((action) => {
|
actions.forEach((action) => {
|
||||||
// akhilmhdh: set it as any due to the union type bug i would end up writing an if else with same condition on both side
|
// akhilmhdh: set it as any due to the union type bug i would end up writing an if else with same condition on both side
|
||||||
if (formVal[rule][action as keyof typeof formVal.workspace]) {
|
if (formVal?.[rule]?.[action as keyof typeof formVal.workspace]) {
|
||||||
permissions.push({ subject: rule, action } as any);
|
permissions.push({ subject: rule, action } as any);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
-149
@@ -1,149 +0,0 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
|
||||||
import { faUserCog } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
|
|
||||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isNonEditable?: boolean;
|
|
||||||
setValue: UseFormSetValue<TFormSchema>;
|
|
||||||
control: Control<TFormSchema>;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Permission {
|
|
||||||
NoAccess = "no-access",
|
|
||||||
ReadOnly = "read-only",
|
|
||||||
FullAccess = "full-acess",
|
|
||||||
Custom = "custom"
|
|
||||||
}
|
|
||||||
|
|
||||||
const PERMISSIONS = [
|
|
||||||
{ action: "read", label: "Read" },
|
|
||||||
{ action: "create", label: "Create" },
|
|
||||||
{ action: "edit", label: "Update" },
|
|
||||||
{ action: "delete", label: "Remove" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const RolePermission = ({ isNonEditable, setValue, control }: Props) => {
|
|
||||||
const rule = useWatch({
|
|
||||||
control,
|
|
||||||
name: "permissions.role"
|
|
||||||
});
|
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
|
||||||
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]);
|
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
|
||||||
if (val === Permission.Custom) setIsCustom.on();
|
|
||||||
else setIsCustom.off();
|
|
||||||
|
|
||||||
switch (val) {
|
|
||||||
case Permission.NoAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.role",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.FullAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.role",
|
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.ReadOnly:
|
|
||||||
setValue(
|
|
||||||
"permissions.role",
|
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setValue(
|
|
||||||
"permissions.role",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
|
||||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div>
|
|
||||||
<FontAwesomeIcon icon={faUserCog} className="text-4xl" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-grow flex flex-col">
|
|
||||||
<div className="font-medium mb-1 text-lg">Role</div>
|
|
||||||
<div className="text-xs font-light">Project role management control</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
defaultValue={Permission.NoAccess}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
value={selectedPermissionCategory}
|
|
||||||
onValueChange={handlePermissionChange}
|
|
||||||
>
|
|
||||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
|
||||||
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<motion.div
|
|
||||||
initial={false}
|
|
||||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
|
||||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
|
||||||
>
|
|
||||||
{isCustom &&
|
|
||||||
PERMISSIONS.map(({ action, label }) => (
|
|
||||||
<Controller
|
|
||||||
name={`permissions.role.${action}`}
|
|
||||||
key={`permissions.role.${action}`}
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
isChecked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
id={`permissions.role.${action}`}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
-149
@@ -1,149 +0,0 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
|
||||||
import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
|
|
||||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isNonEditable?: boolean;
|
|
||||||
setValue: UseFormSetValue<TFormSchema>;
|
|
||||||
control: Control<TFormSchema>;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Permission {
|
|
||||||
NoAccess = "no-access",
|
|
||||||
ReadOnly = "read-only",
|
|
||||||
FullAccess = "full-acess",
|
|
||||||
Custom = "custom"
|
|
||||||
}
|
|
||||||
|
|
||||||
const PERMISSIONS = [
|
|
||||||
{ action: "read", label: "Read" },
|
|
||||||
{ action: "create", label: "Create" },
|
|
||||||
{ action: "edit", label: "Update" },
|
|
||||||
{ action: "delete", label: "Remove" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Props) => {
|
|
||||||
const rule = useWatch({
|
|
||||||
control,
|
|
||||||
name: "permissions.secret-scanning"
|
|
||||||
});
|
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
|
||||||
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]);
|
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
|
||||||
if (val === Permission.Custom) setIsCustom.on();
|
|
||||||
else setIsCustom.off();
|
|
||||||
|
|
||||||
switch (val) {
|
|
||||||
case Permission.NoAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.secret-scanning",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.FullAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.secret-scanning",
|
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.ReadOnly:
|
|
||||||
setValue(
|
|
||||||
"permissions.secret-scanning",
|
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setValue(
|
|
||||||
"permissions.secret-scanning",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
|
||||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div>
|
|
||||||
<FontAwesomeIcon icon={faMagnifyingGlass} className="text-4xl" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-grow flex flex-col">
|
|
||||||
<div className="font-medium mb-1 text-lg">Secret Scanning</div>
|
|
||||||
<div className="text-xs font-light">Secret scanning management control</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
defaultValue={Permission.NoAccess}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
value={selectedPermissionCategory}
|
|
||||||
onValueChange={handlePermissionChange}
|
|
||||||
>
|
|
||||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
|
||||||
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<motion.div
|
|
||||||
initial={false}
|
|
||||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
|
||||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
|
||||||
>
|
|
||||||
{isCustom &&
|
|
||||||
PERMISSIONS.map(({ action, label }) => (
|
|
||||||
<Controller
|
|
||||||
name={`permissions.role.${action}`}
|
|
||||||
key={`permissions.role.${action}`}
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
isChecked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
id={`permissions.role.${action}`}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
-149
@@ -1,149 +0,0 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
|
||||||
import { faCog } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
|
|
||||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isNonEditable?: boolean;
|
|
||||||
setValue: UseFormSetValue<TFormSchema>;
|
|
||||||
control: Control<TFormSchema>;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Permission {
|
|
||||||
NoAccess = "no-access",
|
|
||||||
ReadOnly = "read-only",
|
|
||||||
FullAccess = "full-acess",
|
|
||||||
Custom = "custom"
|
|
||||||
}
|
|
||||||
|
|
||||||
const PERMISSIONS = [
|
|
||||||
{ action: "read", label: "Read" },
|
|
||||||
{ action: "create", label: "Create" },
|
|
||||||
{ action: "edit", label: "Update" },
|
|
||||||
{ action: "delete", label: "Remove" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const SettingsPermission = ({ isNonEditable, setValue, control }: Props) => {
|
|
||||||
const rule = useWatch({
|
|
||||||
control,
|
|
||||||
name: "permissions.settings"
|
|
||||||
});
|
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
|
||||||
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]);
|
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
|
||||||
if (val === Permission.Custom) setIsCustom.on();
|
|
||||||
else setIsCustom.off();
|
|
||||||
|
|
||||||
switch (val) {
|
|
||||||
case Permission.NoAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.settings",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.FullAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.settings",
|
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.ReadOnly:
|
|
||||||
setValue(
|
|
||||||
"permissions.settings",
|
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setValue(
|
|
||||||
"permissions.settings",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
|
||||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div>
|
|
||||||
<FontAwesomeIcon icon={faCog} className="text-4xl" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-grow flex flex-col">
|
|
||||||
<div className="font-medium mb-1 text-lg">Settings</div>
|
|
||||||
<div className="text-xs font-light">Settings management control</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
defaultValue={Permission.NoAccess}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
value={selectedPermissionCategory}
|
|
||||||
onValueChange={handlePermissionChange}
|
|
||||||
>
|
|
||||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
|
||||||
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<motion.div
|
|
||||||
initial={false}
|
|
||||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
|
||||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
|
||||||
>
|
|
||||||
{isCustom &&
|
|
||||||
PERMISSIONS.map(({ action, label }) => (
|
|
||||||
<Controller
|
|
||||||
name={`permissions.service-account.${action}`}
|
|
||||||
key={`permissions.service-account.${action}`}
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
isChecked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
id={`permissions.service-account.${action}`}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+29
-17
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
||||||
import { faMoneyBill } from "@fortawesome/free-solid-svg-icons";
|
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
@@ -11,9 +11,13 @@ import { useToggle } from "@app/hooks";
|
|||||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
formName: keyof Omit<TFormSchema["permissions"], "workspace">;
|
||||||
isNonEditable?: boolean;
|
isNonEditable?: boolean;
|
||||||
setValue: UseFormSetValue<TFormSchema>;
|
setValue: UseFormSetValue<TFormSchema>;
|
||||||
control: Control<TFormSchema>;
|
control: Control<TFormSchema>;
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
icon: IconProp;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Permission {
|
enum Permission {
|
||||||
@@ -24,28 +28,36 @@ enum Permission {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PERMISSIONS = [
|
const PERMISSIONS = [
|
||||||
{ action: "read", label: "Read" },
|
{ action: "read", label: "View" },
|
||||||
{ action: "create", label: "Create" },
|
{ action: "create", label: "Create" },
|
||||||
{ action: "edit", label: "Update" },
|
{ action: "edit", label: "Modify" },
|
||||||
{ action: "delete", label: "Remove" }
|
{ action: "delete", label: "Remove" }
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const BillingPermission = ({ isNonEditable, setValue, control }: Props) => {
|
export const SimpleLevelPermissionOption = ({
|
||||||
|
isNonEditable,
|
||||||
|
setValue,
|
||||||
|
control,
|
||||||
|
formName,
|
||||||
|
subtitle,
|
||||||
|
title,
|
||||||
|
icon
|
||||||
|
}: Props) => {
|
||||||
const rule = useWatch({
|
const rule = useWatch({
|
||||||
control,
|
control,
|
||||||
name: "permissions.billing"
|
name: `permissions.${formName}`
|
||||||
});
|
});
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
const totalActions = PERMISSIONS.length;
|
const totalActions = PERMISSIONS.length;
|
||||||
const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
const score = actions.map((key) => (rule?.[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
if (isCustom) return Permission.Custom;
|
||||||
if (score === 0) return Permission.NoAccess;
|
if (score === 0) return Permission.NoAccess;
|
||||||
if (score === totalActions) return Permission.FullAccess;
|
if (score === totalActions) return Permission.FullAccess;
|
||||||
if (score === 1 && rule.read) return Permission.ReadOnly;
|
if (score === 1 && rule?.read) return Permission.ReadOnly;
|
||||||
|
|
||||||
return Permission.Custom;
|
return Permission.Custom;
|
||||||
}, [rule, isCustom]);
|
}, [rule, isCustom]);
|
||||||
@@ -62,28 +74,28 @@ export const BillingPermission = ({ isNonEditable, setValue, control }: Props) =
|
|||||||
switch (val) {
|
switch (val) {
|
||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.billing",
|
`permissions.${formName}`,
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
{ shouldDirty: true }
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.FullAccess:
|
case Permission.FullAccess:
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.billing",
|
`permissions.${formName}`,
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
{ read: true, edit: true, create: true, delete: true },
|
||||||
{ shouldDirty: true }
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.ReadOnly:
|
case Permission.ReadOnly:
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.billing",
|
`permissions.${formName}`,
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
{ read: true, edit: false, create: false, delete: false },
|
||||||
{ shouldDirty: true }
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.billing",
|
`permissions.${formName}`,
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
{ shouldDirty: true }
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
@@ -100,11 +112,11 @@ export const BillingPermission = ({ isNonEditable, setValue, control }: Props) =
|
|||||||
>
|
>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div>
|
<div>
|
||||||
<FontAwesomeIcon icon={faMoneyBill} className="text-4xl" />
|
<FontAwesomeIcon icon={icon} className="text-4xl" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow flex flex-col">
|
<div className="flex-grow flex flex-col">
|
||||||
<div className="font-medium mb-1 text-lg">Billing</div>
|
<div className="font-medium mb-1 text-lg">{title}</div>
|
||||||
<div className="text-xs font-light">Billing management control</div>
|
<div className="text-xs font-light">{subtitle}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
@@ -128,14 +140,14 @@ export const BillingPermission = ({ isNonEditable, setValue, control }: Props) =
|
|||||||
{isCustom &&
|
{isCustom &&
|
||||||
PERMISSIONS.map(({ action, label }) => (
|
PERMISSIONS.map(({ action, label }) => (
|
||||||
<Controller
|
<Controller
|
||||||
name={`permissions.billing.${action}`}
|
name={`permissions.${formName}.${action}`}
|
||||||
key={`permissions.billing.${action}`}
|
key={`permissions.${formName}.${action}`}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
isChecked={field.value}
|
isChecked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
id={`permissions.billing.${action}`}
|
id={`permissions.${formName}.${action}`}
|
||||||
isDisabled={isNonEditable}
|
isDisabled={isNonEditable}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
-149
@@ -1,149 +0,0 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
|
||||||
import { faSignIn } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
|
|
||||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isNonEditable?: boolean;
|
|
||||||
setValue: UseFormSetValue<TFormSchema>;
|
|
||||||
control: Control<TFormSchema>;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Permission {
|
|
||||||
NoAccess = "no-access",
|
|
||||||
ReadOnly = "read-only",
|
|
||||||
FullAccess = "full-acess",
|
|
||||||
Custom = "custom"
|
|
||||||
}
|
|
||||||
|
|
||||||
const PERMISSIONS = [
|
|
||||||
{ action: "read", label: "Read" },
|
|
||||||
{ action: "create", label: "Create" },
|
|
||||||
{ action: "edit", label: "Update" },
|
|
||||||
{ action: "delete", label: "Remove" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => {
|
|
||||||
const rule = useWatch({
|
|
||||||
control,
|
|
||||||
name: "permissions.sso"
|
|
||||||
});
|
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
|
||||||
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]);
|
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
|
||||||
if (val === Permission.Custom) setIsCustom.on();
|
|
||||||
else setIsCustom.off();
|
|
||||||
|
|
||||||
switch (val) {
|
|
||||||
case Permission.NoAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.sso",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.FullAccess:
|
|
||||||
setValue(
|
|
||||||
"permissions.sso",
|
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case Permission.ReadOnly:
|
|
||||||
setValue(
|
|
||||||
"permissions.sso",
|
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setValue(
|
|
||||||
"permissions.sso",
|
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
|
||||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div>
|
|
||||||
<FontAwesomeIcon icon={faSignIn} className="text-4xl" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-grow flex flex-col">
|
|
||||||
<div className="font-medium mb-1 text-lg">SSO</div>
|
|
||||||
<div className="text-xs font-light">SSO management control</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Select
|
|
||||||
defaultValue={Permission.NoAccess}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
value={selectedPermissionCategory}
|
|
||||||
onValueChange={handlePermissionChange}
|
|
||||||
>
|
|
||||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
|
|
||||||
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
|
|
||||||
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<motion.div
|
|
||||||
initial={false}
|
|
||||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
|
||||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
|
||||||
>
|
|
||||||
{isCustom &&
|
|
||||||
PERMISSIONS.map(({ action, label }) => (
|
|
||||||
<Controller
|
|
||||||
name={`permissions.sso.${action}`}
|
|
||||||
key={`permissions.sso.${action}`}
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Checkbox
|
|
||||||
isChecked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
id={`permissions.sso.${action}`}
|
|
||||||
isDisabled={isNonEditable}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+3
-3
@@ -24,7 +24,7 @@ enum Permission {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PERMISSIONS = [
|
const PERMISSIONS = [
|
||||||
{ action: "read", label: "Read" },
|
{ action: "read", label: "View" },
|
||||||
{ action: "create", label: "Create" }
|
{ action: "create", label: "Create" }
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
@@ -38,12 +38,12 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
|||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
const totalActions = PERMISSIONS.length;
|
const totalActions = PERMISSIONS.length;
|
||||||
const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
const score = actions.map((key) => (rule?.[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
if (isCustom) return Permission.Custom;
|
||||||
if (score === 0) return Permission.NoAccess;
|
if (score === 0) return Permission.NoAccess;
|
||||||
if (score === totalActions) return Permission.FullAccess;
|
if (score === totalActions) return Permission.FullAccess;
|
||||||
if (score === 1 && rule.read) return Permission.ReadOnly;
|
if (score === 1 && rule?.read) return Permission.ReadOnly;
|
||||||
|
|
||||||
return Permission.Custom;
|
return Permission.Custom;
|
||||||
}, [rule, isCustom]);
|
}, [rule, isCustom]);
|
||||||
|
|||||||
+2
-2
@@ -135,9 +135,9 @@ export const MultiEnvProjectPermission = ({
|
|||||||
<Tr>
|
<Tr>
|
||||||
<Th />
|
<Th />
|
||||||
<Th className="min-w-[8rem]">Secret Path</Th>
|
<Th className="min-w-[8rem]">Secret Path</Th>
|
||||||
<Th className="text-center">Read</Th>
|
<Th className="text-center">View</Th>
|
||||||
<Th className="text-center">Create</Th>
|
<Th className="text-center">Create</Th>
|
||||||
<Th className="text-center">Edit</Th>
|
<Th className="text-center">Modify</Th>
|
||||||
<Th className="text-center">Delete</Th>
|
<Th className="text-center">Delete</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
|
|||||||
+1
-1
@@ -260,7 +260,7 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{SINGLE_PERMISSION_LIST.map(({ title, subtitle, icon, formName }) => (
|
{SINGLE_PERMISSION_LIST.map(({ title, subtitle, icon, formName }) => (
|
||||||
<div className="flex flex-col space-y-4" key={`permission-${title}`}>
|
<div key={`permission-${title}`}>
|
||||||
<SingleProjectPermission
|
<SingleProjectPermission
|
||||||
isNonEditable={isNonEditable}
|
isNonEditable={isNonEditable}
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
+5
-2
@@ -94,7 +94,10 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
|
|
||||||
permissions.forEach((permission) => {
|
permissions.forEach((permission) => {
|
||||||
if (["secrets", "folders", "secret-imports"].includes(permission.subject)) {
|
if (["secrets", "folders", "secret-imports"].includes(permission.subject)) {
|
||||||
multiEnvApi2Form(formVal[permission.subject], permission);
|
multiEnvApi2Form(
|
||||||
|
formVal[permission.subject] as TFormSchema["permissions"]["secrets"],
|
||||||
|
permission
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// everything else follows same pattern
|
// everything else follows same pattern
|
||||||
// formVal[settings][read | write] = true
|
// formVal[settings][read | write] = true
|
||||||
@@ -135,7 +138,7 @@ const multiEnvForm2Api = (
|
|||||||
// if not full access for an action
|
// if not full access for an action
|
||||||
if (!formVal?.all?.[action] && action !== "secretPath" && formVal?.[slug]?.[action]) {
|
if (!formVal?.all?.[action] && action !== "secretPath" && formVal?.[slug]?.[action]) {
|
||||||
const conditions: Record<string, unknown> = { environment: slug };
|
const conditions: Record<string, unknown> = { environment: slug };
|
||||||
if (formVal[slug]?.secretPath) conditions.secretPath = formVal[slug].secretPath;
|
if (formVal[slug]?.secretPath) conditions.secretPath = formVal?.[slug]?.secretPath;
|
||||||
|
|
||||||
permissions.push({ action, subject, conditions });
|
permissions.push({ action, subject, conditions });
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -38,9 +38,9 @@ enum Permission {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PERMISSIONS = [
|
const PERMISSIONS = [
|
||||||
{ action: "read", label: "Read" },
|
{ action: "read", label: "View" },
|
||||||
{ action: "create", label: "Create" },
|
{ action: "create", label: "Create" },
|
||||||
{ action: "edit", label: "Update" },
|
{ action: "edit", label: "Modify" },
|
||||||
{ action: "delete", label: "Remove" }
|
{ action: "delete", label: "Remove" }
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import {
|
|||||||
useDeleteSecretV3,
|
useDeleteSecretV3,
|
||||||
useGetFoldersByEnv,
|
useGetFoldersByEnv,
|
||||||
useGetProjectSecretsAllEnv,
|
useGetProjectSecretsAllEnv,
|
||||||
useGetUserWsEnvironments,
|
|
||||||
useGetUserWsKey,
|
useGetUserWsKey,
|
||||||
useUpdateSecretV3
|
useUpdateSecretV3
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
|
|||||||
Reference in New Issue
Block a user