mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 14:26:38 +00:00
Continue progress on project role page
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@ export default function Role() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
<title>{t("common.head-title", { title: "Project Settings" })}</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
<RolePage />
|
<RolePage />
|
||||||
@@ -5,8 +5,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
// import { OrgPermissionCan } from "@app/components/permissions";
|
|
||||||
// import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -18,11 +16,12 @@ import {
|
|||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
// import { withPermission } from "@app/hoc";
|
|
||||||
import { withProjectPermission } from "@app/hoc";
|
import { withProjectPermission } from "@app/hoc";
|
||||||
import { useDeleteOrgRole, useGetOrgRole, useGetProjectRoleBySlug } from "@app/hooks/api";
|
import { useGetProjectRoleBySlug } from "@app/hooks/api";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { RoleDetailsSection, RolePermissionsSection } from "./components";
|
||||||
|
|
||||||
// import { RoleDetailsSection, RoleModal, RolePermissionsSection } from "./components";
|
// import { RoleDetailsSection, RoleModal, RolePermissionsSection } from "./components";
|
||||||
|
|
||||||
export const RolePage = withProjectPermission(
|
export const RolePage = withProjectPermission(
|
||||||
@@ -32,24 +31,17 @@ export const RolePage = withProjectPermission(
|
|||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const projectId = currentWorkspace?.id || "";
|
const projectId = currentWorkspace?.id || "";
|
||||||
|
|
||||||
console.log("RolePage currentWorkspace: ", currentWorkspace);
|
|
||||||
|
|
||||||
// const { currentOrg } = useOrganization();
|
|
||||||
// const orgId = currentOrg?.id || "";
|
|
||||||
|
|
||||||
const { data } = useGetProjectRoleBySlug(currentWorkspace?.slug ?? "", roleSlug as string);
|
const { data } = useGetProjectRoleBySlug(currentWorkspace?.slug ?? "", roleSlug as string);
|
||||||
|
|
||||||
console.log("useGetProjectRoleBySlug data: ", data);
|
|
||||||
|
|
||||||
// const { data } = useGetOrgRole(orgId, roleId); // TODO: get project role
|
// const { data } = useGetOrgRole(orgId, roleId); // TODO: get project role
|
||||||
const { mutateAsync: deleteOrgRole } = useDeleteOrgRole();
|
// const { mutateAsync: deleteOrgRole } = useDeleteOrgRole();
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"role",
|
"role",
|
||||||
"deleteOrgRole"
|
"deleteRole"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const onDeleteOrgRoleSubmit = async () => {
|
const onDeleteRoleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
// if (!orgId || !roleId) return;
|
// if (!orgId || !roleId) return;
|
||||||
// await deleteOrgRole({
|
// await deleteOrgRole({
|
||||||
@@ -65,7 +57,7 @@ export const RolePage = withProjectPermission(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
const error = err as any;
|
const error = err as any;
|
||||||
const text = error?.response?.data?.message ?? "Failed to delete organization role";
|
const text = error?.response?.data?.message ?? "Failed to delete project role";
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text,
|
text,
|
||||||
@@ -136,7 +128,7 @@ export const RolePage = withProjectPermission(
|
|||||||
: "pointer-events-none cursor-not-allowed opacity-50"
|
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
)}
|
)}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
handlePopUpOpen("deleteOrgRole");
|
handlePopUpOpen("deleteRole");
|
||||||
}}
|
}}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
>
|
>
|
||||||
@@ -150,21 +142,19 @@ export const RolePage = withProjectPermission(
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
RoleDetailsSection here
|
<RoleDetailsSection roleSlug={roleSlug} handlePopUpOpen={handlePopUpOpen} />
|
||||||
{/* <RoleDetailsSection roleId={roleId} handlePopUpOpen={handlePopUpOpen} /> */}
|
|
||||||
</div>
|
</div>
|
||||||
RolePermissionsSection here
|
<RolePermissionsSection roleSlug={roleSlug} />
|
||||||
{/* <RolePermissionsSection roleId={roleId} /> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* <RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> */}
|
{/* <RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> */}
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.deleteOrgRole.isOpen}
|
isOpen={popUp.deleteRole.isOpen}
|
||||||
title={`Are you sure want to delete the project role ${data?.name ?? ""}?`}
|
title={`Are you sure want to delete the project role ${data?.name ?? ""}?`}
|
||||||
onChange={(isOpen) => handlePopUpToggle("deleteOrgRole", isOpen)}
|
onChange={(isOpen) => handlePopUpToggle("deleteRole", isOpen)}
|
||||||
deleteKey="confirm"
|
deleteKey="confirm"
|
||||||
onDeleteApproved={() => onDeleteOrgRoleSubmit()}
|
onDeleteApproved={() => onDeleteRoleSubmit()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
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 { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
roleSlug: string;
|
||||||
|
// handlePopUpOpen: (popUpName: keyof UsePopUpState<["role"]>, data?: {}) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RoleDetailsSection = ({
|
||||||
|
roleSlug
|
||||||
|
}: // handlePopUpOpen
|
||||||
|
Props) => {
|
||||||
|
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
|
||||||
|
initialState: "Copy ID to clipboard"
|
||||||
|
});
|
||||||
|
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { data } = useGetProjectRoleBySlug(currentWorkspace?.slug ?? "", roleSlug as string);
|
||||||
|
|
||||||
|
const isCustomRole = !["admin", "member", "viewer", "no-access"].includes(data?.slug ?? "");
|
||||||
|
|
||||||
|
return data ? (
|
||||||
|
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">Details</h3>
|
||||||
|
{isCustomRole && (
|
||||||
|
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Role}>
|
||||||
|
{(isAllowed) => {
|
||||||
|
return (
|
||||||
|
<Tooltip content="Edit Role">
|
||||||
|
<IconButton
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
variant="plain"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => {
|
||||||
|
// TODO
|
||||||
|
// handlePopUpOpen("role", {
|
||||||
|
// roleId
|
||||||
|
// })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPencil} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="pt-4">
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Role ID</p>
|
||||||
|
<div className="group flex align-top">
|
||||||
|
<p className="text-sm text-mineshaft-300">{data.id}</p>
|
||||||
|
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||||
|
<Tooltip content={copyTextId}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
variant="plain"
|
||||||
|
className="group relative ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(data.id);
|
||||||
|
setCopyTextId("Copied");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isCopyingId ? faCheck : faCopy} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Name</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">{data.name}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Slug</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">{data.slug}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-semibold text-mineshaft-300">Description</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">
|
||||||
|
{data.description?.length ? data.description : "-"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
);
|
||||||
|
};
|
||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
||||||
|
import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import { TFormSchema } from "@app/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils";
|
||||||
|
|
||||||
|
const GENERAL_PERMISSIONS = [
|
||||||
|
{ action: "read", label: "View" },
|
||||||
|
{ action: "create", label: "Create" },
|
||||||
|
{ action: "edit", label: "Modify" },
|
||||||
|
{ action: "delete", label: "Remove" }
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const WORKSPACE_PERMISSIONS = [
|
||||||
|
{ action: "edit", label: "Update project details" },
|
||||||
|
{ action: "delete", label: "Delete projects" }
|
||||||
|
] 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 SECRET_ROLLBACK_PERMISSIONS = [
|
||||||
|
{ action: "create", label: "Perform Rollback" },
|
||||||
|
{ action: "read", label: "View" }
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const getPermissionList = (option: Props["formName"]) => {
|
||||||
|
switch (option) {
|
||||||
|
case "workspace":
|
||||||
|
return WORKSPACE_PERMISSIONS;
|
||||||
|
case "member":
|
||||||
|
return MEMBERS_PERMISSIONS;
|
||||||
|
case "secret-rollback":
|
||||||
|
return SECRET_ROLLBACK_PERMISSIONS;
|
||||||
|
default:
|
||||||
|
return GENERAL_PERMISSIONS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type PermissionName =
|
||||||
|
| `permissions.workspace.${"edit" | "delete"}`
|
||||||
|
| `permissions.secret-rollback.${"create" | "read"}`
|
||||||
|
| `permissions.${Exclude<
|
||||||
|
keyof NonNullable<TFormSchema["permissions"]>,
|
||||||
|
"workspace" | "secret-rollback" | "secrets"
|
||||||
|
>}.${"read" | "create" | "edit" | "delete"}`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isEditable: boolean;
|
||||||
|
title: string;
|
||||||
|
formName: keyof Omit<Exclude<TFormSchema["permissions"], undefined>, "secrets">;
|
||||||
|
setValue: UseFormSetValue<TFormSchema>;
|
||||||
|
control: Control<TFormSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Permission {
|
||||||
|
NoAccess = "no-access",
|
||||||
|
ReadOnly = "read-only",
|
||||||
|
FullAccess = "full-acess",
|
||||||
|
Custom = "custom"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RolePermissionRow = ({ isEditable, title, formName, control, setValue }: Props) => {
|
||||||
|
const [isRowExpanded, setIsRowExpanded] = useToggle();
|
||||||
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
|
const rule = useWatch({
|
||||||
|
control,
|
||||||
|
name: `permissions.${formName}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
|
// TODO: re-check
|
||||||
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
|
|
||||||
|
switch (formName) {
|
||||||
|
case "secret-rollback":
|
||||||
|
case "workspace": {
|
||||||
|
const totalActions = WORKSPACE_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;
|
||||||
|
|
||||||
|
return Permission.Custom; // TODO: update
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
const totalActions = GENERAL_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 (rule && "read" in rule) {
|
||||||
|
if (score === 1 && rule?.read) return Permission.ReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Permission.Custom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [rule, isCustom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
|
}, [selectedPermissionCategory]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isRowCustom = selectedPermissionCategory === Permission.Custom;
|
||||||
|
if (isRowCustom) {
|
||||||
|
setIsRowExpanded.on();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handlePermissionChange = (val: Permission) => {
|
||||||
|
if (val === Permission.Custom) {
|
||||||
|
setIsRowExpanded.on();
|
||||||
|
setIsCustom.on();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsCustom.off();
|
||||||
|
|
||||||
|
switch (val) {
|
||||||
|
case Permission.NoAccess:
|
||||||
|
setValue(
|
||||||
|
`permissions.${formName}`,
|
||||||
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case Permission.FullAccess:
|
||||||
|
setValue(
|
||||||
|
`permissions.${formName}`,
|
||||||
|
{ read: true, edit: true, create: true, delete: true },
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case Permission.ReadOnly:
|
||||||
|
setValue(
|
||||||
|
`permissions.${formName}`,
|
||||||
|
{ read: true, edit: false, create: false, delete: false },
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setValue(
|
||||||
|
`permissions.${formName}`,
|
||||||
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tr
|
||||||
|
className="h-10 cursor-pointer transition-colors duration-300 hover:bg-mineshaft-700"
|
||||||
|
onClick={() => setIsRowExpanded.toggle()}
|
||||||
|
>
|
||||||
|
<Td>
|
||||||
|
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||||
|
</Td>
|
||||||
|
<Td>{title}</Td>
|
||||||
|
<Td>
|
||||||
|
<Select
|
||||||
|
value={selectedPermissionCategory}
|
||||||
|
className="w-40 bg-mineshaft-600"
|
||||||
|
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||||
|
onValueChange={handlePermissionChange}
|
||||||
|
isDisabled={!isEditable}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
{isRowExpanded && (
|
||||||
|
<Tr>
|
||||||
|
<Td
|
||||||
|
colSpan={3}
|
||||||
|
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && " border-mineshaft-500 p-8"}`}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
{getPermissionList(formName).map(({ action, label }) => {
|
||||||
|
const permissionName = `permissions.${formName}.${action}` as PermissionName;
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name={permissionName}
|
||||||
|
key={permissionName}
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Checkbox
|
||||||
|
isChecked={field.value}
|
||||||
|
onCheckedChange={(e) => {
|
||||||
|
if (!isEditable) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update default role"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
field.onChange(e);
|
||||||
|
}}
|
||||||
|
id={permissionName}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Checkbox>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import {
|
||||||
|
Control,
|
||||||
|
// Controller,
|
||||||
|
UseFormGetValues,
|
||||||
|
UseFormSetValue,
|
||||||
|
useWatch
|
||||||
|
} from "react-hook-form";
|
||||||
|
import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Select, SelectItem,Td, Tr } from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import { TFormSchema } from "@app/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string;
|
||||||
|
formName: "secrets";
|
||||||
|
isEditable: boolean;
|
||||||
|
setValue: UseFormSetValue<TFormSchema>;
|
||||||
|
getValue: UseFormGetValues<TFormSchema>;
|
||||||
|
control: Control<TFormSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Permission {
|
||||||
|
NoAccess = "no-access",
|
||||||
|
ReadOnly = "read-only",
|
||||||
|
FullAccess = "full-acess",
|
||||||
|
Custom = "custom"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RowPermissionSecretsRow = ({
|
||||||
|
title,
|
||||||
|
formName,
|
||||||
|
isEditable,
|
||||||
|
setValue,
|
||||||
|
getValue,
|
||||||
|
control
|
||||||
|
}: Props) => {
|
||||||
|
const [isRowExpanded, setIsRowExpanded] = useToggle();
|
||||||
|
// const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
|
const allRule = useWatch({ control, name: `permissions.${formName}.all` });
|
||||||
|
|
||||||
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
|
const { read, delete: del, edit, create } = allRule || {};
|
||||||
|
if (read && del && edit && create) return Permission.FullAccess;
|
||||||
|
if (read) return Permission.ReadOnly;
|
||||||
|
return Permission.NoAccess;
|
||||||
|
}, [allRule]);
|
||||||
|
|
||||||
|
const handlePermissionChange = (val: Permission) => {
|
||||||
|
if (!val) return;
|
||||||
|
switch (val) {
|
||||||
|
case Permission.NoAccess: {
|
||||||
|
const permissions = getValue("permissions");
|
||||||
|
if (permissions) delete permissions[formName];
|
||||||
|
setValue("permissions", permissions, { shouldDirty: true });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Permission.FullAccess:
|
||||||
|
setValue(
|
||||||
|
`permissions.${formName}`,
|
||||||
|
{ all: { read: true, edit: true, create: true, delete: true } },
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case Permission.ReadOnly:
|
||||||
|
setValue(
|
||||||
|
`permissions.${formName}`,
|
||||||
|
{ all: { read: true, edit: false, create: false, delete: false } },
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setValue(
|
||||||
|
`permissions.${formName}`,
|
||||||
|
{ custom: { read: false, edit: false, create: false, delete: false } },
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr
|
||||||
|
className="h-10 cursor-pointer transition-colors duration-300 hover:bg-mineshaft-700"
|
||||||
|
onClick={() => setIsRowExpanded.toggle()}
|
||||||
|
>
|
||||||
|
<Td>
|
||||||
|
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||||
|
</Td>
|
||||||
|
<Td>{title}</Td>
|
||||||
|
<Td>
|
||||||
|
<Select
|
||||||
|
value={selectedPermissionCategory}
|
||||||
|
className="w-40 bg-mineshaft-600"
|
||||||
|
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||||
|
onValueChange={handlePermissionChange}
|
||||||
|
isDisabled={!isEditable}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
+199
@@ -0,0 +1,199 @@
|
|||||||
|
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
|
||||||
|
// Td
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { ProjectPermissionSub,useWorkspace } from "@app/context";
|
||||||
|
import {
|
||||||
|
// useUpdateOrgRole,
|
||||||
|
useGetProjectRoleBySlug
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import {
|
||||||
|
formSchema,
|
||||||
|
rolePermission2Form,
|
||||||
|
TFormSchema} from "@app/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils";
|
||||||
|
|
||||||
|
import { RolePermissionRow } from "./RolePermissionRow";
|
||||||
|
|
||||||
|
const SINGLE_PERMISSION_LIST = [
|
||||||
|
{
|
||||||
|
title: "Project",
|
||||||
|
formName: "workspace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Integrations",
|
||||||
|
formName: "integrations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Secret Protect policy",
|
||||||
|
formName: ProjectPermissionSub.SecretApproval
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Roles",
|
||||||
|
formName: "role"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "User management",
|
||||||
|
formName: "member"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Group management",
|
||||||
|
formName: "groups"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Machine identity management",
|
||||||
|
formName: "identity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Webhooks",
|
||||||
|
formName: "webhooks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Service Tokens",
|
||||||
|
formName: "service-tokens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Settings",
|
||||||
|
formName: "settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Environments",
|
||||||
|
formName: "environments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Tags",
|
||||||
|
formName: "tags"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Audit Logs",
|
||||||
|
formName: "audit-logs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "IP Allowlist",
|
||||||
|
formName: "ip-allowlist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Certificate Authorities",
|
||||||
|
formName: "certificate-authorities"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Certificates",
|
||||||
|
formName: "certificates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Secret Rollback",
|
||||||
|
formName: "secret-rollback"
|
||||||
|
}
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
roleSlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: fill table
|
||||||
|
|
||||||
|
export const RolePermissionsSection = ({ roleSlug }: Props) => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { data: role } = useGetProjectRoleBySlug(currentWorkspace?.slug ?? "", roleSlug as string);
|
||||||
|
|
||||||
|
console.log("RolePermissionsSection role data: ", role);
|
||||||
|
|
||||||
|
const {
|
||||||
|
setValue,
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isDirty, isSubmitting },
|
||||||
|
reset
|
||||||
|
} = useForm<TFormSchema>({
|
||||||
|
defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {},
|
||||||
|
resolver: zodResolver(formSchema)
|
||||||
|
});
|
||||||
|
|
||||||
|
// const { mutateAsync: updateRole } = useUpdateOrgRole();
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
try {
|
||||||
|
// await updateRole({
|
||||||
|
// orgId,
|
||||||
|
// id: roleId,
|
||||||
|
// ...el,
|
||||||
|
// permissions: formRolePermission2API(el.permissions)
|
||||||
|
// });
|
||||||
|
createNotification({ type: "success", text: "Successfully updated role" });
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({ type: "error", text: "Failed to update role" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCustomRole = !["admin", "member", "viewer", "no-access"].includes(role?.slug ?? "");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">Permissions</h3>
|
||||||
|
{isCustomRole && (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
type="submit"
|
||||||
|
isDisabled={isSubmitting || !isDirty}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="ml-4 text-mineshaft-300"
|
||||||
|
variant="link"
|
||||||
|
isDisabled={isSubmitting || !isDirty}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
onClick={() => reset()}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="py-4">
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th className="w-5" />
|
||||||
|
<Th>Resource</Th>
|
||||||
|
<Th>Permission</Th>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{SINGLE_PERMISSION_LIST.map((permission) => {
|
||||||
|
console.log("permission: ", permission);
|
||||||
|
return (
|
||||||
|
<RolePermissionRow
|
||||||
|
title={permission.title}
|
||||||
|
formName={permission.formName}
|
||||||
|
control={control}
|
||||||
|
setValue={setValue}
|
||||||
|
key={`project-role-${roleSlug}-permission-${permission.formName}`}
|
||||||
|
isEditable={isCustomRole}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { RolePermissionsSection } from "./RolePermissionsSection";
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { RoleDetailsSection } from "./RoleDetailsSection";
|
||||||
|
export { RolePermissionsSection } from "./RolePermissionsSection";
|
||||||
Reference in New Issue
Block a user