mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 16:26:37 +00:00
feat: switched to a wizard for create identity in project
This commit is contained in:
+100
-38
@@ -1,18 +1,19 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import { subject } from "@casl/ability";
|
import { subject } from "@casl/ability";
|
||||||
import {
|
import {
|
||||||
faArrowDown,
|
faArrowDown,
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
faChevronDown,
|
|
||||||
faCircleXmark,
|
faCircleXmark,
|
||||||
faClock,
|
faClock,
|
||||||
faEllipsisV,
|
faEllipsisV,
|
||||||
faLink,
|
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faPlus,
|
faPlus,
|
||||||
faServer
|
faServer
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { LinkIcon, PlusIcon } from "lucide-react";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
@@ -30,6 +31,8 @@ import {
|
|||||||
HoverCardTrigger,
|
HoverCardTrigger,
|
||||||
IconButton,
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
Pagination,
|
Pagination,
|
||||||
Spinner,
|
Spinner,
|
||||||
Table,
|
Table,
|
||||||
@@ -79,12 +82,20 @@ import { ProjectLinkIdentityModal } from "./components/ProjectLinkIdentityModal"
|
|||||||
|
|
||||||
const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2;
|
const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2;
|
||||||
|
|
||||||
|
enum WizardSteps {
|
||||||
|
SelectAction = "select-action",
|
||||||
|
LinkIdentity = "link-identity",
|
||||||
|
ProjectIdentity = "project-identity"
|
||||||
|
}
|
||||||
|
|
||||||
export const IdentityTab = withProjectPermission(
|
export const IdentityTab = withProjectPermission(
|
||||||
() => {
|
() => {
|
||||||
const { currentProject, projectId } = useProject();
|
const { currentProject, projectId } = useProject();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { isSubOrganization, currentOrg } = useOrganization();
|
const { isSubOrganization, currentOrg } = useOrganization();
|
||||||
|
|
||||||
|
const [wizardStep, setWizardStep] = useState(WizardSteps.SelectAction);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
@@ -133,7 +144,6 @@ export const IdentityTab = withProjectPermission(
|
|||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"createIdentity",
|
"createIdentity",
|
||||||
"linkIdentity",
|
|
||||||
"deleteIdentity",
|
"deleteIdentity",
|
||||||
"upgradePlan",
|
"upgradePlan",
|
||||||
"addOptions"
|
"addOptions"
|
||||||
@@ -201,37 +211,6 @@ export const IdentityTab = withProjectPermission(
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
<DropdownMenu
|
|
||||||
open={popUp.addOptions.isOpen}
|
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("addOptions", isOpen)}
|
|
||||||
>
|
|
||||||
<DropdownMenuTrigger>
|
|
||||||
<Button variant="outline_bg" className="rounded-l-none border-l-mineshaft-800 px-3">
|
|
||||||
<FontAwesomeIcon icon={faChevronDown} />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end" sideOffset={6} className="p-1">
|
|
||||||
<ProjectPermissionCan
|
|
||||||
I={ProjectPermissionActions.Create}
|
|
||||||
a={ProjectPermissionSub.Identity}
|
|
||||||
>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<Button
|
|
||||||
variant="outline_bg"
|
|
||||||
className="w-full"
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faLink} />}
|
|
||||||
onClick={() => {
|
|
||||||
handlePopUpOpen("linkIdentity");
|
|
||||||
handlePopUpClose("addOptions");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Assign Org Identity
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</ProjectPermissionCan>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
@@ -494,11 +473,94 @@ export const IdentityTab = withProjectPermission(
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
<ProjectIdentityModal
|
<Modal
|
||||||
isOpen={popUp.createIdentity.isOpen}
|
isOpen={popUp.createIdentity.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("createIdentity", isOpen)}
|
onOpenChange={(open) => {
|
||||||
/>
|
handlePopUpToggle("createIdentity", open);
|
||||||
<ProjectLinkIdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
if (!open) setWizardStep(WizardSteps.SelectAction);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
bodyClassName="overflow-visible"
|
||||||
|
title="Add Project Identity"
|
||||||
|
subTitle="Create a new identity or assign an existing identity"
|
||||||
|
>
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{wizardStep === WizardSteps.SelectAction && (
|
||||||
|
<motion.div
|
||||||
|
key="select-type-step"
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: -30 }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => setWizardStep(WizardSteps.ProjectIdentity)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
setWizardStep(WizardSteps.ProjectIdentity);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<PlusIcon size="1rem" />
|
||||||
|
<div>Create New Identity</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-xs text-mineshaft-300">
|
||||||
|
Create a new machine identity specifically for this project. This identity
|
||||||
|
will be managed at the project-level.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mt-4 cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => setWizardStep(WizardSteps.LinkIdentity)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
setWizardStep(WizardSteps.LinkIdentity);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<LinkIcon size="1rem" />
|
||||||
|
<div>Assign Existing Identity</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-xs text-mineshaft-300">
|
||||||
|
Assign an existing identity from your organization or namespace to this
|
||||||
|
project. The identity will continue to be managed at its original scope.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
{wizardStep === WizardSteps.ProjectIdentity && (
|
||||||
|
<motion.div
|
||||||
|
key="identity-step"
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: -30 }}
|
||||||
|
>
|
||||||
|
<ProjectIdentityModal onClose={() => handlePopUpClose("createIdentity")} />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
{wizardStep === WizardSteps.LinkIdentity && (
|
||||||
|
<motion.div
|
||||||
|
key="link-step"
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: -30 }}
|
||||||
|
>
|
||||||
|
<ProjectLinkIdentityModal handlePopUpToggle={handlePopUpToggle} />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.deleteIdentity.isOpen}
|
isOpen={popUp.deleteIdentity.isOpen}
|
||||||
title={`Are you sure you want to remove ${
|
title={`Are you sure you want to remove ${
|
||||||
|
|||||||
+1
-22
@@ -13,8 +13,6 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
IconButton,
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
|
||||||
ModalContent,
|
|
||||||
Switch
|
Switch
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useProject } from "@app/context";
|
import { useProject } from "@app/context";
|
||||||
@@ -49,7 +47,7 @@ type ContentProps = {
|
|||||||
identity?: TProjectIdentity;
|
identity?: TProjectIdentity;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Content = ({ onClose, identity }: ContentProps) => {
|
export const ProjectIdentityModal = ({ onClose, identity }: ContentProps) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { currentProject } = useProject();
|
const { currentProject } = useProject();
|
||||||
@@ -289,22 +287,3 @@ const Content = ({ onClose, identity }: ContentProps) => {
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isOpen: boolean;
|
|
||||||
onOpenChange: (isOpen: boolean) => void;
|
|
||||||
identity?: TProjectIdentity;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ProjectIdentityModal = ({ isOpen, onOpenChange, identity }: Props) => {
|
|
||||||
return (
|
|
||||||
<Modal isOpen={isOpen} onOpenChange={(open) => onOpenChange(open)}>
|
|
||||||
<ModalContent
|
|
||||||
bodyClassName="overflow-visible"
|
|
||||||
title={`${identity ? "Update" : "Create"} Project Identity`}
|
|
||||||
>
|
|
||||||
<Content identity={identity} onClose={() => onOpenChange(false)} />
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
+5
-33
@@ -5,15 +5,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import {
|
import { Button, FilterableSelect, FormControl, ModalClose, Spinner } from "@app/components/v2";
|
||||||
Button,
|
|
||||||
FilterableSelect,
|
|
||||||
FormControl,
|
|
||||||
Modal,
|
|
||||||
ModalClose,
|
|
||||||
ModalContent,
|
|
||||||
Spinner
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { useProject } from "@app/context";
|
import { useProject } from "@app/context";
|
||||||
import {
|
import {
|
||||||
projectIdentityMembershipQuery,
|
projectIdentityMembershipQuery,
|
||||||
@@ -34,11 +26,10 @@ const schema = z.object({
|
|||||||
export type FormData = z.infer<typeof schema>;
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
popUp: UsePopUpState<["linkIdentity"]>;
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["createIdentity"]>, state?: boolean) => void;
|
||||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["linkIdentity"]>, state?: boolean) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Content = ({ popUp, handlePopUpToggle }: Props) => {
|
export const ProjectLinkIdentityModal = ({ handlePopUpToggle }: Props) => {
|
||||||
const { projectId } = useProject();
|
const { projectId } = useProject();
|
||||||
|
|
||||||
// const [searchValue, setSearchValue] = useState("");
|
// const [searchValue, setSearchValue] = useState("");
|
||||||
@@ -107,7 +98,7 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
id: nextAvailableMembership?.id
|
id: nextAvailableMembership?.id
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
handlePopUpToggle("linkIdentity", false);
|
handlePopUpToggle("createIdentity", false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isMembershipsLoading || isRolesLoading)
|
if (isMembershipsLoading || isRolesLoading)
|
||||||
@@ -168,7 +159,7 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
isLoading={isSubmitting}
|
isLoading={isSubmitting}
|
||||||
isDisabled={isSubmitting}
|
isDisabled={isSubmitting}
|
||||||
>
|
>
|
||||||
{popUp?.linkIdentity?.data ? "Update" : "Link"}
|
Link
|
||||||
</Button>
|
</Button>
|
||||||
<ModalClose asChild>
|
<ModalClose asChild>
|
||||||
<Button colorSchema="secondary" variant="plain">
|
<Button colorSchema="secondary" variant="plain">
|
||||||
@@ -179,22 +170,3 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectLinkIdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={popUp?.linkIdentity?.isOpen}
|
|
||||||
onOpenChange={(isOpen) => {
|
|
||||||
handlePopUpToggle("linkIdentity", isOpen);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ModalContent
|
|
||||||
title="Assign Existing Identity"
|
|
||||||
subTitle="Assign an existing identity from the parent organization to this project. The identity will continue to be managed at its original scope."
|
|
||||||
bodyClassName="overflow-visible"
|
|
||||||
>
|
|
||||||
<Content popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import { usePopUp } from "@app/hooks";
|
|||||||
import {
|
import {
|
||||||
useAssumeProjectPrivileges,
|
useAssumeProjectPrivileges,
|
||||||
useDeleteProjectIdentityMembership,
|
useDeleteProjectIdentityMembership,
|
||||||
useGetProjectIdentityMembership,
|
|
||||||
useGetProjectIdentityMembershipV2
|
useGetProjectIdentityMembershipV2
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||||
|
|||||||
+13
-4
@@ -22,6 +22,8 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
Tag,
|
Tag,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
@@ -223,11 +225,18 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProjectIdentityModal
|
<Modal
|
||||||
identity={identity}
|
|
||||||
isOpen={popUp.editIdentity.isOpen}
|
isOpen={popUp.editIdentity.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("editIdentity", isOpen)}
|
onOpenChange={(open) => handlePopUpToggle("editIdentity", open)}
|
||||||
/>
|
>
|
||||||
|
<ModalContent bodyClassName="overflow-visible" title="Edit Project Identity">
|
||||||
|
<ProjectIdentityModal
|
||||||
|
identity={identity}
|
||||||
|
onClose={() => handlePopUpToggle("editIdentity", false)}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.deleteIdentity.isOpen}
|
isOpen={popUp.deleteIdentity.isOpen}
|
||||||
title={`Are you sure you want to delete ${identity.name}?`}
|
title={`Are you sure you want to delete ${identity.name}?`}
|
||||||
|
|||||||
Reference in New Issue
Block a user