mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #2176 from Infisical/org-role-cleanup
Cleanup frontend unused org role logic (moved)
This commit is contained in:
@@ -1,249 +0,0 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import {
|
||||
faArrowLeft,
|
||||
faCog,
|
||||
faContactCard,
|
||||
faMagnifyingGlass,
|
||||
faMoneyBill,
|
||||
faServer,
|
||||
faSignIn,
|
||||
faUser,
|
||||
faUserCog,
|
||||
faUsers
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Input } from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { useCreateOrgRole, useUpdateOrgRole } from "@app/hooks/api";
|
||||
import { TOrgRole } from "@app/hooks/api/roles/types";
|
||||
|
||||
import {
|
||||
formRolePermission2API,
|
||||
formSchema,
|
||||
rolePermission2Form,
|
||||
TFormSchema
|
||||
} from "./OrgRoleModifySection.utils";
|
||||
import { SimpleLevelPermissionOption } from "./SimpleLevelPermissionOptions";
|
||||
import { WorkspacePermission } from "./WorkspacePermission";
|
||||
|
||||
type Props = {
|
||||
role?: TOrgRole;
|
||||
onGoBack: VoidFunction;
|
||||
};
|
||||
|
||||
const SIMPLE_PERMISSION_OPTIONS = [
|
||||
{
|
||||
title: "User management",
|
||||
subtitle: "Invite, view and remove users from the organization",
|
||||
icon: faUser,
|
||||
formName: "member"
|
||||
},
|
||||
{
|
||||
title: "Group management",
|
||||
subtitle: "Invite, view and remove user groups from the organization",
|
||||
icon: faUsers,
|
||||
formName: "groups"
|
||||
},
|
||||
{
|
||||
title: "Machine identity management",
|
||||
subtitle: "Create, view, update and remove (machine) identities from the organization",
|
||||
icon: faServer,
|
||||
formName: "identity"
|
||||
},
|
||||
{
|
||||
title: "Billing & usage",
|
||||
subtitle: "Modify organization subscription plan",
|
||||
icon: faMoneyBill,
|
||||
formName: "billing"
|
||||
},
|
||||
{
|
||||
title: "Role management",
|
||||
subtitle: "Create, modify and remove organization roles",
|
||||
icon: faUserCog,
|
||||
formName: "role"
|
||||
},
|
||||
{
|
||||
title: "Incident Contacts",
|
||||
subtitle: "Incident contacts management control",
|
||||
icon: faContactCard,
|
||||
formName: "incident-contact"
|
||||
},
|
||||
{
|
||||
title: "Organization profile",
|
||||
subtitle: "View & update organization metadata such as name",
|
||||
icon: faCog,
|
||||
formName: "settings"
|
||||
},
|
||||
{
|
||||
title: "Secret Scanning",
|
||||
subtitle: "Secret scanning management control",
|
||||
icon: faMagnifyingGlass,
|
||||
formName: "secret-scanning"
|
||||
},
|
||||
{
|
||||
title: "SSO",
|
||||
subtitle: "Define organization level SSO requirements",
|
||||
icon: faSignIn,
|
||||
formName: "sso"
|
||||
},
|
||||
{
|
||||
title: "LDAP",
|
||||
subtitle: "Define organization level LDAP requirements",
|
||||
icon: faSignIn,
|
||||
formName: "ldap"
|
||||
},
|
||||
{
|
||||
title: "SCIM",
|
||||
subtitle: "Define organization level SCIM requirements",
|
||||
icon: faUsers,
|
||||
formName: "scim"
|
||||
}
|
||||
] as const;
|
||||
|
||||
export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||
const isNonEditable = ["owner", "admin", "member", "no-access"].includes(role?.slug || "");
|
||||
const isNewRole = !role?.slug;
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
formState: { isSubmitting, isDirty, errors },
|
||||
setValue,
|
||||
control
|
||||
} = useForm<TFormSchema>({
|
||||
defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {},
|
||||
resolver: zodResolver(formSchema)
|
||||
});
|
||||
|
||||
const { mutateAsync: createRole } = useCreateOrgRole();
|
||||
const { mutateAsync: updateRole } = useUpdateOrgRole();
|
||||
|
||||
const handleRoleUpdate = async (el: TFormSchema) => {
|
||||
if (!role?.id) return;
|
||||
|
||||
try {
|
||||
await updateRole({
|
||||
orgId,
|
||||
id: role?.id,
|
||||
...el,
|
||||
permissions: formRolePermission2API(el.permissions)
|
||||
});
|
||||
createNotification({ type: "success", text: "Successfully updated role" });
|
||||
onGoBack();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({ type: "error", text: "Failed to update role" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleFormSubmit = async (el: TFormSchema) => {
|
||||
if (!isNewRole) {
|
||||
await handleRoleUpdate(el);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await createRole({
|
||||
orgId,
|
||||
...el,
|
||||
permissions: formRolePermission2API(el.permissions)
|
||||
});
|
||||
createNotification({ type: "success", text: "Created new role" });
|
||||
onGoBack();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({ type: "error", text: "Failed to create role" });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold text-mineshaft-100">
|
||||
{isNewRole ? "New" : "Edit"} Role
|
||||
</h1>
|
||||
<Button
|
||||
onClick={onGoBack}
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faArrowLeft} />}
|
||||
>
|
||||
Go back
|
||||
</Button>
|
||||
</div>
|
||||
<p className="mb-8 text-gray-400">
|
||||
Organization-level roles allow you to define permissions for resources at a high level
|
||||
across the organization
|
||||
</p>
|
||||
<div className="flex flex-col space-y-6">
|
||||
<FormControl
|
||||
label="Name"
|
||||
isRequired
|
||||
className="mb-0"
|
||||
isError={Boolean(errors?.name)}
|
||||
errorText={errors?.name?.message}
|
||||
>
|
||||
<Input {...register("name")} placeholder="Billing Team" isReadOnly={isNonEditable} />
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="Slug"
|
||||
isRequired
|
||||
isError={Boolean(errors?.slug)}
|
||||
errorText={errors?.slug?.message}
|
||||
>
|
||||
<Input {...register("slug")} placeholder="biller" isReadOnly={isNonEditable} />
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="Description"
|
||||
helperText="A short description about this role"
|
||||
isError={Boolean(errors?.description)}
|
||||
errorText={errors?.description?.message}
|
||||
>
|
||||
<Input {...register("description")} isReadOnly={isNonEditable} />
|
||||
</FormControl>
|
||||
<div className="flex items-center justify-between border-t border-t-mineshaft-800 pt-6">
|
||||
<div>
|
||||
<h2 className="text-xl font-medium">Add Permission</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<WorkspacePermission
|
||||
isNonEditable={isNonEditable}
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
/>
|
||||
</div>
|
||||
{SIMPLE_PERMISSION_OPTIONS.map(({ title, subtitle, icon, formName }) => (
|
||||
<div key={`permission-${title}`}>
|
||||
<SimpleLevelPermissionOption
|
||||
isNonEditable={isNonEditable}
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
icon={icon}
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
formName={formName}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-12 flex items-center space-x-4">
|
||||
<Button
|
||||
type="submit"
|
||||
isDisabled={isSubmitting || isNonEditable || !isDirty}
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
{isNewRole ? "Create Role" : "Save Role"}
|
||||
</Button>
|
||||
<Button onClick={onGoBack} variant="outline_bg">
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,204 +0,0 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
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 = {
|
||||
formName: keyof Omit<Exclude<TFormSchema["permissions"], undefined>, "workspace">;
|
||||
isNonEditable?: boolean;
|
||||
setValue: UseFormSetValue<TFormSchema>;
|
||||
control: Control<TFormSchema>;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
icon: IconProp;
|
||||
};
|
||||
|
||||
enum Permission {
|
||||
NoAccess = "no-access",
|
||||
ReadOnly = "read-only",
|
||||
FullAccess = "full-acess",
|
||||
Custom = "custom"
|
||||
}
|
||||
|
||||
const PERMISSIONS = [
|
||||
{ action: "read", label: "View" },
|
||||
{ action: "create", label: "Create" },
|
||||
{ action: "edit", label: "Modify" },
|
||||
{ action: "delete", label: "Remove" }
|
||||
] as const;
|
||||
|
||||
const SECRET_SCANNING_PERMISSIONS = [
|
||||
{ action: "read", label: "View risks" },
|
||||
{ action: "create", label: "Add integrations" },
|
||||
{ action: "edit", label: "Edit risk status" },
|
||||
{ action: "delete", label: "Remove integrations" }
|
||||
] as const;
|
||||
|
||||
const INCIDENT_CONTACTS_PERMISSIONS = [
|
||||
{ action: "read", label: "View contacts" },
|
||||
{ action: "create", label: "Add new contacts" },
|
||||
{ action: "edit", label: "Edit contacts" },
|
||||
{ action: "delete", label: "Remove contacts" }
|
||||
] as const;
|
||||
|
||||
const MEMBERS_PERMISSIONS = [
|
||||
{ action: "read", label: "View all members" },
|
||||
{ action: "create", label: "Invite members" },
|
||||
{ action: "edit", label: "Edit members" },
|
||||
{ action: "delete", label: "Remove members" }
|
||||
] as const;
|
||||
|
||||
const BILLING_PERMISSIONS = [
|
||||
{ action: "read", label: "View bills" },
|
||||
{ action: "create", label: "Add payment methods" },
|
||||
{ action: "edit", label: "Edit payments" },
|
||||
{ action: "delete", label: "Remove payments" }
|
||||
] as const;
|
||||
|
||||
const getPermissionList = (option: Props["formName"]) => {
|
||||
switch (option) {
|
||||
case "secret-scanning":
|
||||
return SECRET_SCANNING_PERMISSIONS;
|
||||
case "billing":
|
||||
return BILLING_PERMISSIONS;
|
||||
case "incident-contact":
|
||||
return INCIDENT_CONTACTS_PERMISSIONS;
|
||||
case "member":
|
||||
return MEMBERS_PERMISSIONS;
|
||||
default:
|
||||
return PERMISSIONS;
|
||||
}
|
||||
};
|
||||
|
||||
export const SimpleLevelPermissionOption = ({
|
||||
isNonEditable,
|
||||
setValue,
|
||||
control,
|
||||
formName,
|
||||
subtitle,
|
||||
title,
|
||||
icon
|
||||
}: Props) => {
|
||||
const rule = useWatch({
|
||||
control,
|
||||
name: `permissions.${formName}`
|
||||
});
|
||||
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.${formName}`,
|
||||
{ read: false, edit: false, create: false, delete: false },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
case Permission.FullAccess:
|
||||
setValue(
|
||||
`permissions.${formName}`,
|
||||
{ read: true, edit: true, create: true, delete: true },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
case Permission.ReadOnly:
|
||||
setValue(
|
||||
`permissions.${formName}`,
|
||||
{ read: true, edit: false, create: false, delete: false },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
default:
|
||||
setValue(
|
||||
`permissions.${formName}`,
|
||||
{ read: false, edit: false, create: false, delete: false },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"rounded-md bg-mineshaft-800 px-10 py-6",
|
||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={icon} className="text-4xl" />
|
||||
</div>
|
||||
<div className="flex flex-grow flex-col">
|
||||
<div className="mb-1 text-lg font-medium">{title}</div>
|
||||
<div className="text-xs font-light">{subtitle}</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="grid auto-cols-min grid-flow-col gap-8 overflow-hidden"
|
||||
>
|
||||
{isCustom &&
|
||||
getPermissionList(formName).map(({ action, label }) => (
|
||||
<Controller
|
||||
name={`permissions.${formName}.${action}`}
|
||||
key={`permissions.${formName}.${action}`}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
isChecked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
id={`permissions.${formName}.${action}`}
|
||||
isDisabled={isNonEditable}
|
||||
>
|
||||
{label}
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import { twMerge } from "tailwind-merge";
|
||||
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
||||
import { useToggle } from "@app/hooks";
|
||||
|
||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
||||
import { TFormSchema } from "../../../../RolePage/components/OrgRoleModifySection.utils";
|
||||
|
||||
type Props = {
|
||||
isNonEditable?: boolean;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { OrgRoleModifySection } from "./OrgRoleModifySection";
|
||||
@@ -1,27 +1,9 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { TOrgRole } from "@app/hooks/api/roles/types";
|
||||
|
||||
import { OrgRoleModifySection } from "./OrgRoleModifySection";
|
||||
import { OrgRoleTable } from "./OrgRoleTable";
|
||||
|
||||
export const OrgRoleTabSection = () => {
|
||||
const { popUp, handlePopUpClose } = usePopUp(["editRole"] as const);
|
||||
return popUp.editRole.isOpen ? (
|
||||
<motion.div
|
||||
key="role-modify"
|
||||
transition={{ duration: 0.1 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<OrgRoleModifySection
|
||||
role={popUp.editRole.data as TOrgRole}
|
||||
onGoBack={() => handlePopUpClose("editRole")}
|
||||
/>
|
||||
</motion.div>
|
||||
) : (
|
||||
return (
|
||||
<motion.div
|
||||
key="role-list"
|
||||
transition={{ duration: 0.1 }}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { TFormSchema } from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils";
|
||||
import { TFormSchema } from "@app/views/Org/RolePage/components/OrgRoleModifySection.utils";
|
||||
|
||||
const PERMISSIONS = [
|
||||
{ action: "read", label: "View" },
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button , Table, TableContainer, TBody, Th, THead, Tr } from "@app/components/v2";
|
||||
import { Button, Table, TableContainer, TBody, Th, THead, Tr } from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api";
|
||||
import {
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
formSchema,
|
||||
rolePermission2Form,
|
||||
TFormSchema
|
||||
} from "@app/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils";
|
||||
} from "@app/views/Org/RolePage/components/OrgRoleModifySection.utils";
|
||||
|
||||
import { RolePermissionRow } from "./RolePermissionRow";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user