mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 18:15:53 +00:00
203 lines
6.8 KiB
TypeScript
203 lines
6.8 KiB
TypeScript
import { useState } from "react";
|
|
import { useFormContext } from "react-hook-form";
|
|
|
|
import { createNotification } from "@app/components/notifications";
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
Button,
|
|
Modal,
|
|
ModalClose,
|
|
ModalContent
|
|
} from "@app/components/v2";
|
|
import { ProjectPermissionSub } from "@app/context";
|
|
import { useGetProjectTypeFromRoute } from "@app/hooks";
|
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
|
|
|
import {
|
|
PROJECT_PERMISSION_OBJECT,
|
|
RoleTemplate,
|
|
RoleTemplates,
|
|
TFormSchema
|
|
} from "./ProjectRoleModifySection.utils";
|
|
|
|
type Props = {
|
|
isOpen: boolean;
|
|
onOpenChange: (isOpen: boolean) => void;
|
|
};
|
|
|
|
type ContentProps = {
|
|
onClose: () => void;
|
|
};
|
|
|
|
const Content = ({ onClose }: ContentProps) => {
|
|
const rootForm = useFormContext<TFormSchema>();
|
|
const projectType = useGetProjectTypeFromRoute();
|
|
|
|
const [selectedTemplate, setSelectedTemplate] = useState<RoleTemplate>();
|
|
const [conflictingSubjects, setConflictingSubjects] = useState<ProjectPermissionSub[]>([]);
|
|
const [showConflictingSubjects, setShowConflictingSubjects] = useState(false);
|
|
|
|
const templates = RoleTemplates[projectType ?? ProjectType.SecretManager];
|
|
|
|
const onSubmit = (skipConflicting = false) => {
|
|
if (!selectedTemplate) {
|
|
createNotification({ type: "error", text: "Please select a template" });
|
|
return;
|
|
}
|
|
|
|
selectedTemplate.permissions.forEach(({ subject, actions }) => {
|
|
if (skipConflicting && conflictingSubjects.includes(subject)) return;
|
|
|
|
rootForm.setValue(
|
|
`permissions.${subject}`,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
|
|
[Object.fromEntries(actions.map((action) => [action, true]))],
|
|
{
|
|
shouldDirty: true,
|
|
shouldTouch: true,
|
|
shouldValidate: true
|
|
}
|
|
);
|
|
});
|
|
|
|
onClose();
|
|
};
|
|
|
|
const onApply = () => {
|
|
if (!selectedTemplate) {
|
|
createNotification({ type: "error", text: "Please select a template" });
|
|
return;
|
|
}
|
|
|
|
const conflictingPolicies: ProjectPermissionSub[] = [];
|
|
|
|
selectedTemplate.permissions.forEach(({ subject }) => {
|
|
const rootPolicyValue = rootForm.getValues("permissions")?.[subject];
|
|
|
|
if (rootPolicyValue?.length) {
|
|
conflictingPolicies.push(subject);
|
|
}
|
|
});
|
|
|
|
if (conflictingPolicies.length) {
|
|
setConflictingSubjects(conflictingPolicies);
|
|
setShowConflictingSubjects(true);
|
|
return;
|
|
}
|
|
|
|
onSubmit();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Modal isOpen={showConflictingSubjects} onOpenChange={setShowConflictingSubjects}>
|
|
<ModalContent
|
|
title="Conflicting Policies"
|
|
subTitle="The following resources already have policies assigned to them."
|
|
>
|
|
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
{conflictingSubjects.map((subject) => (
|
|
<div key={subject}>
|
|
<span className="text-mineshaft-200">
|
|
{PROJECT_PERMISSION_OBJECT[subject].title}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="mt-8 flex space-x-4">
|
|
<ModalClose asChild>
|
|
<Button colorSchema="danger" onClick={() => onSubmit()}>
|
|
Overwrite Existing
|
|
</Button>
|
|
</ModalClose>
|
|
<ModalClose asChild>
|
|
<Button colorSchema="secondary" onClick={() => onSubmit(true)}>
|
|
Skip Conflicting
|
|
</Button>
|
|
</ModalClose>
|
|
</div>
|
|
</ModalContent>
|
|
</Modal>
|
|
<Accordion
|
|
type="single"
|
|
value={selectedTemplate?.id}
|
|
onValueChange={(value) =>
|
|
setSelectedTemplate(templates.find((template) => template.id === value))
|
|
}
|
|
collapsible
|
|
className="w-full border-collapse"
|
|
>
|
|
{templates.map(({ name, description, permissions, id }) => (
|
|
<AccordionItem
|
|
key={id}
|
|
value={id}
|
|
className="m-0 border border-mineshaft-600 first:rounded-t last:rounded-b data-[state=open]:border-primary/40 data-[state=open]:bg-mineshaft-600/30"
|
|
>
|
|
<AccordionTrigger className="w-full justify-start p-4 py-8 text-mineshaft-100 hover:bg-mineshaft-700 hover:text-mineshaft-100 data-[state=open]:bg-primary/[3%] data-[state=open]:text-mineshaft-100">
|
|
<div className="mr-auto flex flex-col py-2 text-left">
|
|
<span>{name}</span>
|
|
<span className="text-sm leading-3 text-mineshaft-400">{description}</span>
|
|
</div>
|
|
</AccordionTrigger>
|
|
<AccordionContent className="border-t border-mineshaft-600">
|
|
<div className="thin-scrollbar max-h-[20rem] overflow-y-auto">
|
|
<span className="text-mineshaft-300">Grants the following permissions:</span>
|
|
<div className="grid grid-cols-2 gap-4 py-2">
|
|
{permissions
|
|
.map((permission) => ({
|
|
...permission,
|
|
object: PROJECT_PERMISSION_OBJECT[permission.subject]
|
|
}))
|
|
.sort((a, b) => a.object.title.localeCompare(b.object.title))
|
|
.map(({ subject, actions, object }) => {
|
|
return (
|
|
<div key={subject}>
|
|
<span className="text-mineshaft-200">{object.title}</span>
|
|
<ul className="mt-1 flex list-disc flex-col gap-1 pl-4">
|
|
{actions.map((action) => (
|
|
<li key={action}>
|
|
{object.actions.find((a) => a.value === action)?.label}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
))}
|
|
</Accordion>
|
|
<div className="mt-8 flex space-x-4">
|
|
<Button isDisabled={!selectedTemplate} onClick={onApply}>
|
|
Apply Template
|
|
</Button>
|
|
<ModalClose asChild>
|
|
<Button colorSchema="secondary" variant="plain">
|
|
Cancel
|
|
</Button>
|
|
</ModalClose>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export const PolicyTemplateModal = ({ isOpen, onOpenChange }: Props) => {
|
|
return (
|
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
|
<ModalContent
|
|
title="Policy Templates"
|
|
subTitle="Select a template with prepopulated policies to get started. You can always add more policies later."
|
|
className="max-w-3xl"
|
|
>
|
|
<Content onClose={() => onOpenChange(false)} />
|
|
</ModalContent>
|
|
</Modal>
|
|
);
|
|
};
|