mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
feat(ui): finished ui for identity additional privilege
This commit is contained in:
@ -41,6 +41,18 @@ export type IdentityMembership = {
|
||||
temporaryAccessStartTime: string | null;
|
||||
temporaryAccessEndTime: string | null;
|
||||
}[];
|
||||
additionalPrivileges: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null | undefined;
|
||||
slug: string;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessEndTime: string | null | undefined;
|
||||
temporaryAccessStartTime: string | null | undefined;
|
||||
isTemporary: boolean;
|
||||
createdAt: string;
|
||||
}[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
@ -0,0 +1,7 @@
|
||||
export {
|
||||
useCreateIdentityProjectAdditionalPrivilege,
|
||||
useDeleteIdentityProjectAdditionalPrivilege,
|
||||
useUpdateIdentityProjectAdditionalPrivilege
|
||||
} from "./mutation";
|
||||
export { useGetIdentityProjectPrivilegeDetails } from "./queries";
|
||||
export type { TIdentityProjectPrivilege } from "./types";
|
@ -0,0 +1,74 @@
|
||||
import { packRules } from "@casl/ability/extra";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workspaceKeys } from "../workspace/queries";
|
||||
import {
|
||||
TCreateIdentityProjectPrivilegeDTO,
|
||||
TDeleteIdentityProjectPrivilegeDTO,
|
||||
TIdentityProjectPrivilege,
|
||||
TUpdateIdentityProjectPrivlegeDTO
|
||||
} from "./types";
|
||||
|
||||
export const useCreateIdentityProjectAdditionalPrivilege = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
{ privilege: TIdentityProjectPrivilege },
|
||||
{},
|
||||
TCreateIdentityProjectPrivilegeDTO
|
||||
>({
|
||||
mutationFn: async (dto) => {
|
||||
const { data } = await apiRequest.post("/api/v1/additional-privilege/identity", {
|
||||
...dto,
|
||||
permissions: packRules(dto.permissions)
|
||||
});
|
||||
return data.privilege;
|
||||
},
|
||||
onSuccess: (_, { projectId }) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIdentityMemberships(projectId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateIdentityProjectAdditionalPrivilege = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
{ privilege: TIdentityProjectPrivilege },
|
||||
{},
|
||||
TUpdateIdentityProjectPrivlegeDTO
|
||||
>({
|
||||
mutationFn: async (dto) => {
|
||||
const { data } = await apiRequest.patch(
|
||||
`/api/v1/additional-privilege/identity/${dto.privilegeId}`,
|
||||
{ ...dto, permissions: dto.permissions ? packRules(dto.permissions) : undefined }
|
||||
);
|
||||
return data.privilege;
|
||||
},
|
||||
onSuccess: (_, { projectId }) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIdentityMemberships(projectId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIdentityProjectAdditionalPrivilege = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
{ privilege: TIdentityProjectPrivilege },
|
||||
{},
|
||||
TDeleteIdentityProjectPrivilegeDTO
|
||||
>({
|
||||
mutationFn: async (dto) => {
|
||||
const { data } = await apiRequest.delete(
|
||||
`/api/v1/additional-privilege/identity/${dto.privilegeId}`
|
||||
);
|
||||
return data.privilege;
|
||||
},
|
||||
onSuccess: (_, { projectId }) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIdentityMemberships(projectId));
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
import { PackRule, unpackRules } from "@casl/ability/extra";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TProjectPermission } from "../roles/types";
|
||||
import { TIdentityProjectPrivilege } from "./types";
|
||||
|
||||
export const identitiyProjectPrivilegeKeys = {
|
||||
details: (privilegeId: string) => ["project-user-privilege", { privilegeId }] as const
|
||||
};
|
||||
|
||||
const fetchIdentityProjectPrivilegeDetails = async (privilegeId: string) => {
|
||||
const {
|
||||
data: { privilege }
|
||||
} = await apiRequest.get<{
|
||||
privilege: Omit<TIdentityProjectPrivilege, "permissions"> & { permissions: unknown };
|
||||
}>(`/api/v1/additional-privilege/identity/${privilegeId}`);
|
||||
return {
|
||||
...privilege,
|
||||
permissions: unpackRules(privilege.permissions as PackRule<TProjectPermission>[])
|
||||
};
|
||||
};
|
||||
|
||||
export const useGetIdentityProjectPrivilegeDetails = (privilegeId: string) => {
|
||||
return useQuery({
|
||||
enabled: Boolean(privilegeId),
|
||||
queryKey: identitiyProjectPrivilegeKeys.details(privilegeId),
|
||||
queryFn: () => fetchIdentityProjectPrivilegeDetails(privilegeId)
|
||||
});
|
||||
};
|
@ -0,0 +1,48 @@
|
||||
import { TProjectPermission } from "../roles/types";
|
||||
|
||||
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
||||
Relative = "relative"
|
||||
}
|
||||
|
||||
export type TIdentityProjectPrivilege = {
|
||||
projectMembershipId: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
isTemporary: boolean;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description?: string | null | undefined;
|
||||
temporaryMode?: string | null | undefined;
|
||||
temporaryRange?: string | null | undefined;
|
||||
temporaryAccessStartTime?: string | null | undefined;
|
||||
temporaryAccessEndTime?: Date | null | undefined;
|
||||
permissions?: TProjectPermission[];
|
||||
};
|
||||
|
||||
export type TCreateIdentityProjectPrivilegeDTO = {
|
||||
identityId: string;
|
||||
projectId: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
isTemporary?: boolean;
|
||||
temporaryMode?: IdentityProjectAdditionalPrivilegeTemporaryMode;
|
||||
temporaryRange?: string;
|
||||
temporaryAccessStartTime?: string;
|
||||
permissions: TProjectPermission[];
|
||||
};
|
||||
|
||||
export type TUpdateIdentityProjectPrivlegeDTO = {
|
||||
privilegeId: string;
|
||||
projectId: string;
|
||||
} & Partial<Omit<TCreateIdentityProjectPrivilegeDTO, "projectMembershipId" | "projectId">>;
|
||||
|
||||
export type TDeleteIdentityProjectPrivilegeDTO = {
|
||||
privilegeId: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TGetIdentityProejctPrivilegeDetails = {
|
||||
privilegeId: string;
|
||||
};
|
@ -6,6 +6,7 @@ export * from "./bots";
|
||||
export * from "./dynamicSecret";
|
||||
export * from "./dynamicSecretLease";
|
||||
export * from "./identities";
|
||||
export * from "./identityProjectAdditionalPrivilege";
|
||||
export * from "./incidentContacts";
|
||||
export * from "./integrationAuth";
|
||||
export * from "./integrations";
|
||||
|
@ -22,8 +22,11 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
|
||||
import { Button, FormControl, Input } from "@app/components/v2";
|
||||
import { ProjectPermissionSub } from "@app/context";
|
||||
import {
|
||||
useCreateIdentityProjectAdditionalPrivilege,
|
||||
useCreateProjectUserAdditionalPrivilege,
|
||||
useGetIdentityProjectPrivilegeDetails,
|
||||
useGetProjectUserPrivilegeDetails,
|
||||
useUpdateIdentityProjectAdditionalPrivilege,
|
||||
useUpdateProjectUserAdditionalPrivilege
|
||||
} from "@app/hooks/api";
|
||||
|
||||
@ -32,7 +35,6 @@ import {
|
||||
formRolePermission2API,
|
||||
formSchema,
|
||||
rolePermission2Form,
|
||||
// rolePermission2Form,
|
||||
TFormSchema
|
||||
} from "../ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils";
|
||||
import { SecretRollbackPermission } from "../ProjectRoleListTab/components/ProjectRoleModifySection/SecretRollbackPermission";
|
||||
@ -124,14 +126,26 @@ type Props = {
|
||||
actorId: string;
|
||||
};
|
||||
|
||||
export const AdditionalPrivilegeForm = ({ onGoBack, privilegeId, actorId, workspaceId }: Props) => {
|
||||
export const AdditionalPrivilegeForm = ({
|
||||
onGoBack,
|
||||
privilegeId,
|
||||
actorId,
|
||||
workspaceId,
|
||||
isIdentity
|
||||
}: Props) => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const isNewRole = !privilegeId;
|
||||
|
||||
const { data: projectUserPrivilegeDetails } = useGetProjectUserPrivilegeDetails(
|
||||
privilegeId || ""
|
||||
privilegeId && !isIdentity ? privilegeId : ""
|
||||
);
|
||||
|
||||
const { data: identityProjectPrivilegeDetails } = useGetIdentityProjectPrivilegeDetails(
|
||||
isIdentity && privilegeId ? privilegeId : ""
|
||||
);
|
||||
|
||||
const privileges = isIdentity ? identityProjectPrivilegeDetails : projectUserPrivilegeDetails;
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
@ -141,24 +155,36 @@ export const AdditionalPrivilegeForm = ({ onGoBack, privilegeId, actorId, worksp
|
||||
control
|
||||
} = useForm<TFormSchema>({
|
||||
resolver: zodResolver(formSchema),
|
||||
values: projectUserPrivilegeDetails && {
|
||||
...projectUserPrivilegeDetails,
|
||||
description: projectUserPrivilegeDetails.description || "",
|
||||
permissions: rolePermission2Form(projectUserPrivilegeDetails.permissions)
|
||||
values: privileges && {
|
||||
...privileges,
|
||||
description: privileges.description || "",
|
||||
permissions: rolePermission2Form(privileges.permissions)
|
||||
}
|
||||
});
|
||||
|
||||
const createProjectUserAdditionalPrivilege = useCreateProjectUserAdditionalPrivilege();
|
||||
const updateProjectUserAdditionalPrivilege = useUpdateProjectUserAdditionalPrivilege();
|
||||
|
||||
const createIdentityProjectAdditionalPrivilege = useCreateIdentityProjectAdditionalPrivilege();
|
||||
const updateIdentityProjectAdditionalPrivilege = useUpdateIdentityProjectAdditionalPrivilege();
|
||||
|
||||
const handleRoleUpdate = async (el: TFormSchema) => {
|
||||
try {
|
||||
await updateProjectUserAdditionalPrivilege.mutateAsync({
|
||||
...el,
|
||||
permissions: formRolePermission2API(el.permissions),
|
||||
privilegeId: privilegeId as string,
|
||||
workspaceId
|
||||
});
|
||||
if (isIdentity) {
|
||||
await updateIdentityProjectAdditionalPrivilege.mutateAsync({
|
||||
...el,
|
||||
permissions: formRolePermission2API(el.permissions),
|
||||
privilegeId: privilegeId as string,
|
||||
projectId: workspaceId
|
||||
});
|
||||
} else {
|
||||
await updateProjectUserAdditionalPrivilege.mutateAsync({
|
||||
...el,
|
||||
permissions: formRolePermission2API(el.permissions),
|
||||
privilegeId: privilegeId as string,
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
createNotification({ type: "success", text: "Successfully update privilege" });
|
||||
onGoBack();
|
||||
} catch (err) {
|
||||
@ -174,12 +200,21 @@ export const AdditionalPrivilegeForm = ({ onGoBack, privilegeId, actorId, worksp
|
||||
}
|
||||
|
||||
try {
|
||||
await createProjectUserAdditionalPrivilege.mutateAsync({
|
||||
...el,
|
||||
permissions: formRolePermission2API(el.permissions),
|
||||
projectMembershipId: actorId,
|
||||
workspaceId
|
||||
});
|
||||
if (isIdentity) {
|
||||
await createIdentityProjectAdditionalPrivilege.mutateAsync({
|
||||
...el,
|
||||
permissions: formRolePermission2API(el.permissions),
|
||||
identityId: actorId,
|
||||
projectId: workspaceId
|
||||
});
|
||||
} else {
|
||||
await createProjectUserAdditionalPrivilege.mutateAsync({
|
||||
...el,
|
||||
permissions: formRolePermission2API(el.permissions),
|
||||
projectMembershipId: actorId,
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
createNotification({ type: "success", text: "Created new privilege" });
|
||||
onGoBack();
|
||||
} catch (err) {
|
||||
|
@ -19,7 +19,10 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteProjectUserAdditionalPrivilege } from "@app/hooks/api";
|
||||
import {
|
||||
useDeleteIdentityProjectAdditionalPrivilege,
|
||||
useDeleteProjectUserAdditionalPrivilege
|
||||
} from "@app/hooks/api";
|
||||
import { TWorkspaceUser } from "@app/hooks/api/types";
|
||||
|
||||
import { AdditionalPrivilegeForm } from "./AdditionalPrivilegeForm";
|
||||
@ -28,40 +31,51 @@ import { AdditionalPrivilegeTemporaryAccess } from "./AdditionalPrivilegeTempora
|
||||
type Props = {
|
||||
onGoBack: VoidFunction;
|
||||
name: string;
|
||||
projectMembershipId: string;
|
||||
isIdentity?: boolean;
|
||||
// isIdentity id - identity id else projectMembershipId
|
||||
actorId: string;
|
||||
privileges: TWorkspaceUser["additionalPrivileges"];
|
||||
};
|
||||
|
||||
export const AdditionalPrivilegeSection = ({
|
||||
onGoBack,
|
||||
privileges = [],
|
||||
projectMembershipId,
|
||||
name
|
||||
actorId,
|
||||
name,
|
||||
isIdentity
|
||||
}: Props) => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||
"modifyPrivilege",
|
||||
"deletePrivilege"
|
||||
] as const);
|
||||
const { createNotification } = useNotificationContext();
|
||||
const deleteProjectUserAdditionalPrivilege = useDeleteProjectUserAdditionalPrivilege();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const deleteProjectUserAdditionalPrivilege = useDeleteProjectUserAdditionalPrivilege();
|
||||
const deleteProjectIdentityAdditionalPrivilege = useDeleteIdentityProjectAdditionalPrivilege();
|
||||
|
||||
const onPrivilegeDelete = async (privilegeId: string) => {
|
||||
try {
|
||||
await deleteProjectUserAdditionalPrivilege.mutateAsync({
|
||||
privilegeId,
|
||||
workspaceId
|
||||
});
|
||||
if (isIdentity) {
|
||||
await deleteProjectIdentityAdditionalPrivilege.mutateAsync({
|
||||
privilegeId,
|
||||
projectId: workspaceId
|
||||
});
|
||||
} else {
|
||||
await deleteProjectUserAdditionalPrivilege.mutateAsync({
|
||||
privilegeId,
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
handlePopUpClose("deletePrivilege");
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully removed user privilege"
|
||||
text: "Successfully removed privilege"
|
||||
});
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Failed to delete user privilege"
|
||||
text: "Failed to delete privilege"
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -83,7 +97,8 @@ export const AdditionalPrivilegeSection = ({
|
||||
onGoBack={() => handlePopUpClose("modifyPrivilege")}
|
||||
privilegeId={privilegeDetails?.id}
|
||||
workspaceId={workspaceId}
|
||||
actorId={projectMembershipId}
|
||||
isIdentity={isIdentity}
|
||||
actorId={actorId}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
@ -119,7 +134,11 @@ export const AdditionalPrivilegeSection = ({
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col space-y-4">
|
||||
{privileges.length === 0 && (
|
||||
<EmptyState title="User has no additional privileges" iconSize="3x" icon={faUserShield} />
|
||||
<EmptyState
|
||||
title={`${isIdentity ? "Machine identity" : "User"} has no additional privileges`}
|
||||
iconSize="3x"
|
||||
icon={faUserShield}
|
||||
/>
|
||||
)}
|
||||
{privileges.map(({ id, name: privilegeName, description, slug, ...dto }) => (
|
||||
<div
|
||||
@ -137,6 +156,7 @@ export const AdditionalPrivilegeSection = ({
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<AdditionalPrivilegeTemporaryAccess
|
||||
isIdentity={isIdentity}
|
||||
privilegeId={id}
|
||||
workspaceId={workspaceId}
|
||||
temporaryConfig={!dto.isTemporary ? { isTemporary: false } : { ...dto }}
|
||||
|
@ -18,7 +18,11 @@ import {
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useUpdateProjectUserAdditionalPrivilege } from "@app/hooks/api";
|
||||
import {
|
||||
useUpdateIdentityProjectAdditionalPrivilege,
|
||||
useUpdateProjectUserAdditionalPrivilege
|
||||
} from "@app/hooks/api";
|
||||
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/identityProjectAdditionalPrivilege/types";
|
||||
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/projectUserAdditionalPrivilege/types";
|
||||
|
||||
const temporaryRoleFormSchema = z.object({
|
||||
@ -30,6 +34,7 @@ type TTemporaryRoleFormSchema = z.infer<typeof temporaryRoleFormSchema>;
|
||||
type TTemporaryRoleFormProps = {
|
||||
privilegeId: string;
|
||||
workspaceId: string;
|
||||
isIdentity?: boolean;
|
||||
temporaryConfig?: {
|
||||
isTemporary?: boolean;
|
||||
temporaryAccessEndTime?: string | null;
|
||||
@ -41,7 +46,8 @@ type TTemporaryRoleFormProps = {
|
||||
export const AdditionalPrivilegeTemporaryAccess = ({
|
||||
temporaryConfig: defaultValues = {},
|
||||
workspaceId,
|
||||
privilegeId
|
||||
privilegeId,
|
||||
isIdentity
|
||||
}: TTemporaryRoleFormProps) => {
|
||||
const { popUp, handlePopUpToggle } = usePopUp(["setTempRole"] as const);
|
||||
const { createNotification } = useNotificationContext();
|
||||
@ -56,17 +62,29 @@ export const AdditionalPrivilegeTemporaryAccess = ({
|
||||
isTemporaryFieldValue && new Date() > new Date(defaultValues.temporaryAccessEndTime || "");
|
||||
|
||||
const updateProjectUserAdditionalPrivilege = useUpdateProjectUserAdditionalPrivilege();
|
||||
const updateProjectIdentityAdditionalPrivilege = useUpdateIdentityProjectAdditionalPrivilege();
|
||||
|
||||
const handleGrantTemporaryAccess = async (el: TTemporaryRoleFormSchema) => {
|
||||
try {
|
||||
await updateProjectUserAdditionalPrivilege.mutateAsync({
|
||||
privilegeId: privilegeId as string,
|
||||
workspaceId,
|
||||
isTemporary: true,
|
||||
temporaryRange: el.temporaryRange,
|
||||
temporaryAccessStartTime: new Date().toISOString(),
|
||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative
|
||||
});
|
||||
if (isIdentity) {
|
||||
await updateProjectIdentityAdditionalPrivilege.mutateAsync({
|
||||
privilegeId: privilegeId as string,
|
||||
projectId: workspaceId,
|
||||
isTemporary: true,
|
||||
temporaryRange: el.temporaryRange,
|
||||
temporaryAccessStartTime: new Date().toISOString(),
|
||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative
|
||||
});
|
||||
} else {
|
||||
await updateProjectUserAdditionalPrivilege.mutateAsync({
|
||||
privilegeId: privilegeId as string,
|
||||
workspaceId,
|
||||
isTemporary: true,
|
||||
temporaryRange: el.temporaryRange,
|
||||
temporaryAccessStartTime: new Date().toISOString(),
|
||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative
|
||||
});
|
||||
}
|
||||
createNotification({ type: "success", text: "Successfully updated access" });
|
||||
handlePopUpToggle("setTempRole");
|
||||
} catch (err) {
|
||||
@ -77,11 +95,19 @@ export const AdditionalPrivilegeTemporaryAccess = ({
|
||||
|
||||
const handleRevokeTemporaryAccess = async () => {
|
||||
try {
|
||||
await updateProjectUserAdditionalPrivilege.mutateAsync({
|
||||
privilegeId: privilegeId as string,
|
||||
workspaceId,
|
||||
isTemporary: false
|
||||
});
|
||||
if (isIdentity) {
|
||||
await updateProjectIdentityAdditionalPrivilege.mutateAsync({
|
||||
privilegeId: privilegeId as string,
|
||||
projectId: workspaceId,
|
||||
isTemporary: false
|
||||
});
|
||||
} else {
|
||||
await updateProjectUserAdditionalPrivilege.mutateAsync({
|
||||
privilegeId: privilegeId as string,
|
||||
workspaceId,
|
||||
isTemporary: false
|
||||
});
|
||||
}
|
||||
createNotification({ type: "success", text: "Successfully updated access" });
|
||||
handlePopUpToggle("setTempRole");
|
||||
} catch (err) {
|
||||
|
@ -1,17 +1,270 @@
|
||||
import Link from "next/link";
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
faPlus,
|
||||
faServer,
|
||||
faUserShield,
|
||||
faXmark
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
import { motion } from "framer-motion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { IdentitySection } from "./components";
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
export const IdentityTab = () => {
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-identity"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<IdentitySection />
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
import { AdditionalPrivilegeSection } from "../AdditionalPrivilegeSection";
|
||||
import { IdentityModal } from "./components/IdentityModal";
|
||||
import { IdentityRoles } from "./components/IdentityRoles";
|
||||
|
||||
export const IdentityTab = withProjectPermission(
|
||||
() => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const workspaceId = currentWorkspace?.id ?? "";
|
||||
|
||||
const { data, isLoading } = useGetWorkspaceIdentityMemberships(currentWorkspace?.id || "");
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"identity",
|
||||
"deleteIdentity",
|
||||
"upgradePlan",
|
||||
"additionalPrivilege"
|
||||
] as const);
|
||||
|
||||
const onRemoveIdentitySubmit = async (identityId: string) => {
|
||||
try {
|
||||
await deleteMutateAsync({
|
||||
identityId,
|
||||
workspaceId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully removed identity from project",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteIdentity");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = err as any;
|
||||
const text = error?.response?.data?.message ?? "Failed to remove identity from project";
|
||||
|
||||
createNotification({
|
||||
text,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (popUp.additionalPrivilege.isOpen) {
|
||||
const privilegeDetails = popUp?.additionalPrivilege?.data as {
|
||||
name: string;
|
||||
index: number;
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-additional-permission"
|
||||
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<AdditionalPrivilegeSection
|
||||
isIdentity
|
||||
onGoBack={() => handlePopUpClose("additionalPrivilege")}
|
||||
privileges={data?.[privilegeDetails.index]?.additionalPrivileges || []}
|
||||
name={privilegeDetails.name}
|
||||
actorId={privilegeDetails.identityId}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-identity"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
|
||||
<div className="flex w-full justify-end pr-4">
|
||||
<Link href="https://infisical.com/docs/documentation/platform/identities/overview">
|
||||
<span className="w-max cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
||||
Documentation{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.06rem] ml-1 text-xs"
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={ProjectPermissionSub.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("identity")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add identity
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Role</Th>
|
||||
<Th>Added on</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && <TableSkeleton columns={7} innerKey="project-identities" />}
|
||||
{!isLoading &&
|
||||
data &&
|
||||
data.length > 0 &&
|
||||
data.map(
|
||||
({ identity: { id, name }, roles, createdAt, additionalPrivileges }, index) => {
|
||||
const hasAdditionalPrivilege = Boolean(additionalPrivileges.length);
|
||||
return (
|
||||
<Tr className="h-10" key={`st-v3-${id}`}>
|
||||
<Td>{name}</Td>
|
||||
<Td>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IdentityRoles
|
||||
roles={roles}
|
||||
disableEdit={!isAllowed}
|
||||
identityId={id}
|
||||
/>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</Td>
|
||||
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||
<Td className="flex justify-end">
|
||||
<div className="flex items-center space-x-2">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.Member}
|
||||
allowedLabel="Additional Privilege"
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
size="lg"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className={twMerge(hasAdditionalPrivilege && "text-primary")}
|
||||
isDisabled={!isAllowed}
|
||||
onClick={() =>
|
||||
handlePopUpOpen("additionalPrivilege", {
|
||||
name,
|
||||
index,
|
||||
identityId: id
|
||||
})
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faUserShield} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handlePopUpOpen("deleteIdentity", {
|
||||
identityId: id,
|
||||
name
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
)}
|
||||
{!isLoading && data && data?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={7}>
|
||||
<EmptyState
|
||||
title="No identities have been added to this project"
|
||||
icon={faServer}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteIdentity.isOpen}
|
||||
title={`Are you sure want to remove ${(popUp?.deleteIdentity?.data as { name: string })?.name || ""
|
||||
} from the project?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onRemoveIdentitySubmit(
|
||||
(popUp?.deleteIdentity?.data as { identityId: string })?.identityId
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
},
|
||||
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Identity }
|
||||
);
|
||||
|
@ -1,107 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
import { useDeleteIdentityFromWorkspace } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { IdentityModal } from "./IdentityModal";
|
||||
import { IdentityTable } from "./IdentityTable";
|
||||
|
||||
export const IdentitySection = withProjectPermission(
|
||||
() => {
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const workspaceId = currentWorkspace?.id ?? "";
|
||||
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"identity",
|
||||
"deleteIdentity",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const onRemoveIdentitySubmit = async (identityId: string) => {
|
||||
try {
|
||||
await deleteMutateAsync({
|
||||
identityId,
|
||||
workspaceId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully removed identity from project",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteIdentity");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = err as any;
|
||||
const text = error?.response?.data?.message ?? "Failed to remove identity from project";
|
||||
|
||||
createNotification({
|
||||
text,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
|
||||
<div className="flex w-full justify-end pr-4">
|
||||
<Link href="https://infisical.com/docs/documentation/platform/identities/overview">
|
||||
<span className="w-max cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
||||
Documentation{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.06rem] ml-1 text-xs"
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={ProjectPermissionSub.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("identity")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add identity
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<IdentityTable handlePopUpOpen={handlePopUpOpen} />
|
||||
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteIdentity.isOpen}
|
||||
title={`Are you sure want to remove ${
|
||||
(popUp?.deleteIdentity?.data as { name: string })?.name || ""
|
||||
} from the project?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onRemoveIdentitySubmit(
|
||||
(popUp?.deleteIdentity?.data as { identityId: string })?.identityId
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Identity }
|
||||
);
|
@ -1,108 +0,0 @@
|
||||
import { faServer, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { useGetWorkspaceIdentityMemberships } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { IdentityRoles } from "./IdentityRoles";
|
||||
|
||||
type Props = {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["deleteIdentity", "identity"]>,
|
||||
data?: {
|
||||
identityId?: string;
|
||||
name?: string;
|
||||
}
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data, isLoading } = useGetWorkspaceIdentityMemberships(currentWorkspace?.id || "");
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Role</Th>
|
||||
<Th>Added on</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && <TableSkeleton columns={7} innerKey="project-identities" />}
|
||||
{!isLoading &&
|
||||
data &&
|
||||
data.length > 0 &&
|
||||
data.map(({ identity: { id, name }, roles, createdAt }) => {
|
||||
return (
|
||||
<Tr className="h-10" key={`st-v3-${id}`}>
|
||||
<Td>{name}</Td>
|
||||
<Td>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IdentityRoles roles={roles} disableEdit={!isAllowed} identityId={id} />
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</Td>
|
||||
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||
<Td className="flex justify-end">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handlePopUpOpen("deleteIdentity", {
|
||||
identityId: id,
|
||||
name
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
{!isLoading && data && data?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={7}>
|
||||
<EmptyState title="No identities have been added to this project" icon={faServer} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export { IdentitySection } from "./IdentitySection";
|
@ -1 +0,0 @@
|
||||
export { IdentitySection } from "./IdentitySection";
|
@ -215,7 +215,7 @@ export const MemberListTab = () => {
|
||||
onGoBack={() => handlePopUpClose("additionalPrivilege")}
|
||||
privileges={members?.[privilegeDetails.index]?.additionalPrivileges || []}
|
||||
name={privilegeDetails.name}
|
||||
projectMembershipId={privilegeDetails.projectMembershipId}
|
||||
actorId={privilegeDetails.projectMembershipId}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
|
Reference in New Issue
Block a user