mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 21:27:10 +00:00
Merge branch 'heads/main' into ENG-3630
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
@@ -0,0 +1,45 @@
|
||||
import { components, OptionProps } from "react-select";
|
||||
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faBuilding, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Badge, Tooltip } from "@app/components/v2";
|
||||
import { TAvailableAppConnection } from "@app/hooks/api/appConnections";
|
||||
|
||||
export const AppConnectionOption = ({
|
||||
isSelected,
|
||||
children,
|
||||
...props
|
||||
}: OptionProps<TAvailableAppConnection>) => {
|
||||
const isCreateOption = props.data.id === "_create";
|
||||
|
||||
return (
|
||||
<components.Option isSelected={isSelected} {...props}>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
{isCreateOption ? (
|
||||
<div className="flex items-center gap-x-1 text-mineshaft-400">
|
||||
<FontAwesomeIcon icon={faPlus} size="sm" />
|
||||
<span className="mr-auto">Create New Connection</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="truncate">{children}</p>
|
||||
{!props.data.projectId && (
|
||||
<Tooltip content="This connection belongs to your organization.">
|
||||
<div className="ml-2 mr-auto">
|
||||
<Badge className="flex h-5 w-min items-center gap-1 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300 hover:text-bunker-300">
|
||||
<FontAwesomeIcon icon={faBuilding} size="sm" />
|
||||
Organization
|
||||
</Badge>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isSelected && (
|
||||
<FontAwesomeIcon className="ml-2 text-primary" icon={faCheckCircle} size="sm" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./AppConnectionOption";
|
||||
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, useParams } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { useOrganization, useWorkspace } from "@app/context";
|
||||
import { useOrganization, useProject } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
|
||||
import { createNotification } from "../notifications";
|
||||
@@ -51,7 +51,7 @@ export default function NavHeader({
|
||||
isProtectedBranch = false,
|
||||
protectionPolicyName
|
||||
}: Props): JSX.Element {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentProject } = useProject();
|
||||
const { currentOrg } = useOrganization();
|
||||
|
||||
const [isCopied, { timedToggle: toggleIsCopied }] = useToggle(false);
|
||||
@@ -79,7 +79,7 @@ export default function NavHeader({
|
||||
<>
|
||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-xs text-gray-400" />
|
||||
<div className="truncate text-sm font-semibold text-bunker-300">
|
||||
{currentWorkspace?.name}
|
||||
{currentProject?.name}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -93,7 +93,7 @@ export default function NavHeader({
|
||||
{pageName === "Secrets" ? (
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/overview"
|
||||
params={{ projectId: currentWorkspace.id }}
|
||||
params={{ projectId: currentProject.id }}
|
||||
className="text-sm font-semibold text-primary/80 hover:text-primary"
|
||||
>
|
||||
{pageName}
|
||||
@@ -129,7 +129,7 @@ export default function NavHeader({
|
||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-xs text-gray-400" />
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/secrets/$envSlug"
|
||||
params={{ projectId: currentWorkspace.id, envSlug: routerEnvSlug }}
|
||||
params={{ projectId: currentProject.id, envSlug: routerEnvSlug }}
|
||||
className="text-sm font-semibold text-primary/80 hover:text-primary"
|
||||
>
|
||||
{userAvailableEnvs?.find(({ slug }) => slug === currentEnv)?.name}
|
||||
@@ -192,7 +192,7 @@ export default function NavHeader({
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/secrets/$envSlug"
|
||||
params={{
|
||||
projectId: currentWorkspace.id,
|
||||
projectId: currentProject.id,
|
||||
envSlug: routerEnvSlug || ""
|
||||
}}
|
||||
search={(query) => ({ ...query, secretPath: newSecretPath })}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useFormContext, useWatch } from "react-hook-form";
|
||||
import { MongoAbility, MongoQuery } from "@casl/ability";
|
||||
import { Edge, Node, useEdgesState, useNodesState } from "@xyflow/react";
|
||||
|
||||
import { ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { ProjectPermissionSub, useProject } from "@app/context";
|
||||
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
|
||||
import { useListProjectEnvironmentsFolders } from "@app/hooks/api/secretFolders/queries";
|
||||
import { TSecretFolderWithPath } from "@app/hooks/api/secretFolders/types";
|
||||
@@ -36,15 +36,15 @@ export const useAccessTree = (
|
||||
searchPath: string,
|
||||
subject: ProjectPermissionSub
|
||||
) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentProject } = useProject();
|
||||
const { secretName, setSecretName, setViewMode, viewMode } = useAccessTreeContext();
|
||||
const { control } = useFormContext<AccessTreeForm>();
|
||||
const metadata = useWatch({ control, name: "metadata" });
|
||||
const [nodes, setNodes] = useNodesState<Node>([]);
|
||||
const [edges, setEdges] = useEdgesState<Edge>([]);
|
||||
const [environment, setEnvironment] = useState(currentWorkspace.environments[0]?.slug ?? "");
|
||||
const [environment, setEnvironment] = useState(currentProject.environments[0]?.slug ?? "");
|
||||
const { data: environmentsFolders, isPending } = useListProjectEnvironmentsFolders(
|
||||
currentWorkspace.id
|
||||
currentProject.id
|
||||
);
|
||||
|
||||
const [levelFolderMap, setLevelFolderMap] = useState<LevelFolderMap>({});
|
||||
@@ -279,7 +279,7 @@ export const useAccessTree = (
|
||||
environment,
|
||||
setEnvironment,
|
||||
isLoading: isPending,
|
||||
environments: currentWorkspace.environments,
|
||||
environments: currentProject.environments,
|
||||
secretName,
|
||||
setSecretName,
|
||||
viewMode,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { FunctionComponent, ReactNode } from "react";
|
||||
import { BoundCanProps, Can } from "@casl/react";
|
||||
import { AbilityTuple, MongoAbility } from "@casl/ability";
|
||||
import { Can } from "@casl/react";
|
||||
|
||||
import { TOrgPermission, useOrgPermission } from "@app/context/OrgPermissionContext";
|
||||
import { TooltipProps } from "@app/components/v2/Tooltip/Tooltip";
|
||||
import { useOrgPermission } from "@app/context/OrgPermissionContext";
|
||||
import { OrgPermissionSet } from "@app/context/OrgPermissionContext/types";
|
||||
|
||||
import { AccessRestrictedBanner, Tooltip } from "../v2";
|
||||
|
||||
@@ -13,22 +16,33 @@ export const OrgPermissionGuardBanner = () => {
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
type Props<T extends AbilityTuple> = {
|
||||
label?: ReactNode;
|
||||
// this prop is used when there exist already a tooltip as helper text for users
|
||||
// so when permission is allowed same tooltip will be reused to show helpertext
|
||||
renderTooltip?: boolean;
|
||||
allowedLabel?: string;
|
||||
renderGuardBanner?: boolean;
|
||||
} & BoundCanProps<TOrgPermission>;
|
||||
tooltipProps?: Omit<TooltipProps, "children">;
|
||||
I: T[0];
|
||||
ability?: MongoAbility<T>;
|
||||
children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode);
|
||||
passThrough?: boolean;
|
||||
} & (
|
||||
| { an: T[1] }
|
||||
| {
|
||||
a: T[1];
|
||||
}
|
||||
);
|
||||
|
||||
export const OrgPermissionCan: FunctionComponent<Props> = ({
|
||||
export const OrgPermissionCan: FunctionComponent<Props<OrgPermissionSet>> = ({
|
||||
label = "Access restricted",
|
||||
children,
|
||||
passThrough = true,
|
||||
renderTooltip,
|
||||
allowedLabel,
|
||||
renderGuardBanner,
|
||||
tooltipProps,
|
||||
...props
|
||||
}) => {
|
||||
const { permission } = useOrgPermission();
|
||||
@@ -38,16 +52,22 @@ export const OrgPermissionCan: FunctionComponent<Props> = ({
|
||||
{(isAllowed, ability) => {
|
||||
// akhilmhdh: This is set as type due to error in casl react type.
|
||||
const finalChild =
|
||||
typeof children === "function"
|
||||
? children(isAllowed, ability as TOrgPermission)
|
||||
: children;
|
||||
typeof children === "function" ? children(isAllowed, ability as any) : children;
|
||||
|
||||
if (!isAllowed && passThrough) {
|
||||
return <Tooltip content={label}>{finalChild}</Tooltip>;
|
||||
return (
|
||||
<Tooltip content={label} {...tooltipProps}>
|
||||
{finalChild}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed && renderTooltip && allowedLabel) {
|
||||
return <Tooltip content={allowedLabel}>{finalChild}</Tooltip>;
|
||||
return (
|
||||
<Tooltip content={allowedLabel} {...tooltipProps}>
|
||||
{finalChild}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAllowed && renderGuardBanner) {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { OrgPermissionCan } from "./OrgPermissionCan";
|
||||
import { ProjectPermissionCan } from "./ProjectPermissionCan";
|
||||
|
||||
interface PermissionCanProps {
|
||||
type: "project" | "org";
|
||||
I: any;
|
||||
a: any;
|
||||
children: (isAllowed: boolean, ability?: any) => React.ReactNode;
|
||||
}
|
||||
|
||||
export const VariablePermissionCan = ({ type, children, ...props }: PermissionCanProps) => {
|
||||
if (type === "project") {
|
||||
return <ProjectPermissionCan {...props}>{children}</ProjectPermissionCan>;
|
||||
}
|
||||
|
||||
return <OrgPermissionCan {...props}>{children}</OrgPermissionCan>;
|
||||
};
|
||||
@@ -3,3 +3,4 @@ export { GlobPermissionInfo } from "./GlobPermissionInfo";
|
||||
export { OrgPermissionCan } from "./OrgPermissionCan";
|
||||
export { PermissionDeniedBanner } from "./PermissionDeniedBanner";
|
||||
export { ProjectPermissionCan } from "./ProjectPermissionCan";
|
||||
export * from "./VariablePermissionCan";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { z } from "zod";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, FormControl, Input, TextArea } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
|
||||
import { useUpdateProject } from "@app/hooks/api";
|
||||
|
||||
const baseFormSchema = z.object({
|
||||
@@ -37,35 +37,35 @@ type Props = {
|
||||
};
|
||||
|
||||
export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentProject } = useProject();
|
||||
const { mutateAsync, isPending } = useUpdateProject();
|
||||
const { handleSubmit, control, reset, watch } = useForm<BaseFormData | FormDataWithSlug>({
|
||||
resolver: zodResolver(showSlugField ? formSchemaWithSlug : baseFormSchema)
|
||||
});
|
||||
|
||||
const currentSlug = showSlugField ? watch("slug") : currentWorkspace?.slug;
|
||||
const currentSlug = showSlugField ? watch("slug") : currentProject?.slug;
|
||||
|
||||
useEffect(() => {
|
||||
if (currentWorkspace) {
|
||||
if (currentProject) {
|
||||
reset({
|
||||
name: currentWorkspace.name,
|
||||
description: currentWorkspace.description ?? "",
|
||||
...(showSlugField && { slug: currentWorkspace.slug })
|
||||
name: currentProject.name,
|
||||
description: currentProject.description ?? "",
|
||||
...(showSlugField && { slug: currentProject.slug })
|
||||
});
|
||||
}
|
||||
}, [currentWorkspace, showSlugField]);
|
||||
}, [currentProject, showSlugField]);
|
||||
|
||||
const onFormSubmit = async (data: BaseFormData | FormDataWithSlug) => {
|
||||
try {
|
||||
if (!currentWorkspace?.id) return;
|
||||
if (!currentProject?.id) return;
|
||||
|
||||
await mutateAsync({
|
||||
projectID: currentWorkspace.id,
|
||||
projectId: currentProject.id,
|
||||
newProjectName: data.name,
|
||||
newProjectDescription: data.description,
|
||||
...(showSlugField &&
|
||||
"slug" in data && {
|
||||
newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined
|
||||
newSlug: data.slug !== currentProject.slug ? data.slug : undefined
|
||||
})
|
||||
});
|
||||
|
||||
@@ -105,7 +105,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
|
||||
variant="outline_bg"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(currentWorkspace?.id || "");
|
||||
navigator.clipboard.writeText(currentProject?.id || "");
|
||||
createNotification({
|
||||
text: "Copied project ID to clipboard",
|
||||
type: "success"
|
||||
|
||||
@@ -34,10 +34,10 @@ import {
|
||||
useUser
|
||||
} from "@app/context";
|
||||
import { getProjectHomePage, getProjectLottieIcon } from "@app/helpers/project";
|
||||
import { useCreateWorkspace, useGetExternalKmsList, useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { useCreateWorkspace, useGetExternalKmsList, useGetUserProjects } from "@app/hooks/api";
|
||||
import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
import { InfisicalProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().trim().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
|
||||
@@ -89,7 +89,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
||||
const { permission } = useOrgPermission();
|
||||
const { user } = useUser();
|
||||
const createWs = useCreateWorkspace();
|
||||
const { refetch: refetchWorkspaces } = useGetUserWorkspaces();
|
||||
const { refetch: refetchWorkspaces } = useGetUserProjects();
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const canReadProjectTemplates = permission.can(
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Input, Modal, ModalClose, ModalContent } from "@app/components/v2";
|
||||
import { useRequestProjectAccess } from "@app/hooks/api";
|
||||
import { Project } from "@app/hooks/api/projects/types";
|
||||
|
||||
type ContentProps = {
|
||||
projectId: string;
|
||||
onComplete: () => void;
|
||||
};
|
||||
|
||||
const Content = ({ projectId, onComplete }: ContentProps) => {
|
||||
const form = useForm<{ note: string }>();
|
||||
|
||||
const requestProjectAccess = useRequestProjectAccess();
|
||||
|
||||
const onFormSubmit = ({ note }: { note: string }) => {
|
||||
if (requestProjectAccess.isPending) return;
|
||||
requestProjectAccess.mutate(
|
||||
{
|
||||
comment: note,
|
||||
projectId
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
createNotification({
|
||||
type: "success",
|
||||
title: "Project Access Request Sent",
|
||||
text: "Project admins will receive an email of your request"
|
||||
});
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<FormControl label="Note">
|
||||
<Input {...form.register("note")} />
|
||||
</FormControl>
|
||||
<div className="mt-4 flex items-center">
|
||||
<Button className="mr-4" size="sm" type="submit" isLoading={form.formState.isSubmitting}>
|
||||
Submit Request
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
type RequestProjectAccessModalProps = {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
project?: Project;
|
||||
onComplete?: () => void;
|
||||
};
|
||||
|
||||
export const RequestProjectAccessModal = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
project,
|
||||
onComplete
|
||||
}: RequestProjectAccessModalProps) => {
|
||||
if (!project) return null;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent
|
||||
title="Confirm Access Request"
|
||||
subTitle={`Requesting access to project ${project?.name}. You may include an optional note for project admins to review your request.`}
|
||||
>
|
||||
<Content
|
||||
onComplete={() => {
|
||||
onOpenChange(false);
|
||||
if (onComplete) onComplete();
|
||||
}}
|
||||
projectId={project?.id}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export { NewProjectModal } from "./NewProjectModal";
|
||||
export * from "./RequestProjectAccessModal";
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
||||
|
||||
import { SecretRotationV2Form } from "@app/components/secret-rotations-v2/forms";
|
||||
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
|
||||
import { SecretRotationV2ModalHeader } from "@app/components/secret-rotations-v2/SecretRotationV2ModalHeader";
|
||||
import { SecretRotationV2Select } from "@app/components/secret-rotations-v2/SecretRotationV2Select";
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { ProjectEnv } from "@app/hooks/api/projects/types";
|
||||
import { SecretRotation, TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
|
||||
import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
|
||||
|
||||
type SharedProps = {
|
||||
secretPath: string;
|
||||
environment?: string;
|
||||
environments?: WorkspaceEnv[];
|
||||
environments?: ProjectEnv[];
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@@ -24,14 +26,23 @@ type ContentProps = {
|
||||
onComplete: (secretRotation: TSecretRotationV2) => void;
|
||||
selectedRotation: SecretRotation | null;
|
||||
setSelectedRotation: (selectedRotation: SecretRotation | null) => void;
|
||||
initialFormData?: Partial<TSecretRotationV2Form>;
|
||||
onCancel: () => void;
|
||||
} & SharedProps;
|
||||
|
||||
const Content = ({ setSelectedRotation, selectedRotation, ...props }: ContentProps) => {
|
||||
const Content = ({
|
||||
setSelectedRotation,
|
||||
selectedRotation,
|
||||
initialFormData,
|
||||
onCancel,
|
||||
...props
|
||||
}: ContentProps) => {
|
||||
if (selectedRotation) {
|
||||
return (
|
||||
<SecretRotationV2Form
|
||||
onCancel={() => setSelectedRotation(null)}
|
||||
onCancel={onCancel}
|
||||
type={selectedRotation}
|
||||
initialFormData={initialFormData}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -42,12 +53,60 @@ const Content = ({ setSelectedRotation, selectedRotation, ...props }: ContentPro
|
||||
|
||||
export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }: Props) => {
|
||||
const [selectedRotation, setSelectedRotation] = useState<SecretRotation | null>(null);
|
||||
const [initialFormData, setInitialFormData] = useState<Partial<TSecretRotationV2Form>>();
|
||||
|
||||
const {
|
||||
location: {
|
||||
search: { connectionId, connectionName, ...search },
|
||||
pathname
|
||||
}
|
||||
} = useRouterState();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (connectionId && connectionName) {
|
||||
const storedFormData = localStorage.getItem("secretRotationFormData");
|
||||
|
||||
if (!storedFormData) return;
|
||||
|
||||
let form: Partial<TSecretRotationV2Form> = {};
|
||||
try {
|
||||
form = JSON.parse(storedFormData) as TSecretRotationV2Form;
|
||||
} catch {
|
||||
return;
|
||||
} finally {
|
||||
localStorage.removeItem("secretRotationFormData");
|
||||
}
|
||||
|
||||
onOpenChange(true);
|
||||
|
||||
setSelectedRotation(form.type ?? null);
|
||||
|
||||
setInitialFormData({
|
||||
...form,
|
||||
connection: { id: connectionId, name: connectionName }
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: pathname,
|
||||
search
|
||||
});
|
||||
}
|
||||
}, [connectionId, connectionName]);
|
||||
|
||||
const handleReset = () => {
|
||||
setSelectedRotation(null);
|
||||
setInitialFormData(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setSelectedRotation(null);
|
||||
if (!open) {
|
||||
handleReset();
|
||||
}
|
||||
onOpenChange(open);
|
||||
}}
|
||||
>
|
||||
@@ -84,9 +143,11 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }:
|
||||
>
|
||||
<Content
|
||||
onComplete={() => {
|
||||
setSelectedRotation(null);
|
||||
handleReset();
|
||||
onOpenChange(false);
|
||||
}}
|
||||
onCancel={handleReset}
|
||||
initialFormData={initialFormData}
|
||||
selectedRotation={selectedRotation}
|
||||
setSelectedRotation={setSelectedRotation}
|
||||
{...props}
|
||||
|
||||
+2
-2
@@ -9,14 +9,14 @@ import {
|
||||
IS_ROTATION_DUAL_CREDENTIALS,
|
||||
SECRET_ROTATION_MAP
|
||||
} from "@app/helpers/secretRotationsV2";
|
||||
import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
|
||||
import { ProjectEnv } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { TSecretRotationV2Form } from "./schemas";
|
||||
import { SecretRotationV2ConnectionField } from "./SecretRotationV2ConnectionField";
|
||||
|
||||
type Props = {
|
||||
isUpdate: boolean;
|
||||
environments?: WorkspaceEnv[];
|
||||
environments?: ProjectEnv[];
|
||||
};
|
||||
|
||||
export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }: Props) => {
|
||||
|
||||
+50
-21
@@ -1,14 +1,17 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { SingleValue } from "react-select";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
import { AppConnectionOption } from "@app/components/app-connections";
|
||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||
import { OrgPermissionSubjects, useOrgPermission } from "@app/context";
|
||||
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { ProjectPermissionSub, useProject, useProjectPermission } from "@app/context";
|
||||
import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||
import { SECRET_ROTATION_CONNECTION_MAP } from "@app/helpers/secretRotationsV2";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useListAvailableAppConnections } from "@app/hooks/api/appConnections";
|
||||
import { AddAppConnectionModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components";
|
||||
|
||||
import { TSecretRotationV2Form } from "./schemas";
|
||||
|
||||
@@ -18,19 +21,26 @@ type Props = {
|
||||
};
|
||||
|
||||
export const SecretRotationV2ConnectionField = ({ onChange: callback, isUpdate }: Props) => {
|
||||
const { permission } = useOrgPermission();
|
||||
const { control, watch } = useFormContext<TSecretRotationV2Form>();
|
||||
const { permission } = useProjectPermission();
|
||||
const { control, watch, setValue } = useFormContext<TSecretRotationV2Form>();
|
||||
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["addConnection"] as const);
|
||||
|
||||
const rotationType = watch("type");
|
||||
const app = SECRET_ROTATION_CONNECTION_MAP[rotationType];
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(app);
|
||||
const { currentProject } = useProject();
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(
|
||||
app,
|
||||
currentProject.id
|
||||
);
|
||||
|
||||
const connectionName = APP_CONNECTION_MAP[app].name;
|
||||
|
||||
const canCreateConnection = permission.can(
|
||||
OrgPermissionAppConnectionActions.Create,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
const appName = APP_CONNECTION_MAP[app].name;
|
||||
@@ -66,37 +76,56 @@ export const SecretRotationV2ConnectionField = ({ onChange: callback, isUpdate }
|
||||
<FilterableSelect
|
||||
value={value}
|
||||
onChange={(newValue) => {
|
||||
if ((newValue as SingleValue<{ id: string; name: string }>)?.id === "_create") {
|
||||
handlePopUpOpen("addConnection");
|
||||
onChange(null);
|
||||
// store for oauth callback connections
|
||||
localStorage.setItem("secretRotationFormData", JSON.stringify(watch()));
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(newValue);
|
||||
if (callback) callback();
|
||||
}}
|
||||
isLoading={isPending}
|
||||
options={availableConnections}
|
||||
options={[
|
||||
...(canCreateConnection ? [{ id: "_create", name: "Create Connection" }] : []),
|
||||
...(availableConnections ?? [])
|
||||
]}
|
||||
isDisabled={isUpdate}
|
||||
placeholder="Select connection..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
components={{ Option: AppConnectionOption }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="connection"
|
||||
/>
|
||||
{!isUpdate && availableConnections?.length === 0 && (
|
||||
{!isUpdate && !isPending && !availableConnections?.length && !canCreateConnection && (
|
||||
<p className="-mt-2.5 mb-2.5 text-xs text-yellow">
|
||||
<FontAwesomeIcon className="mr-1" size="xs" icon={faInfoCircle} />
|
||||
{canCreateConnection ? (
|
||||
<>
|
||||
You do not have access to any {appName} Connections. Create one from the{" "}
|
||||
<Link to="/organization/app-connections" className="underline">
|
||||
App Connections
|
||||
</Link>{" "}
|
||||
page.
|
||||
</>
|
||||
) : (
|
||||
`You do not have access to any ${appName} Connections. Contact an admin to create one.`
|
||||
)}
|
||||
You do not have access to any {appName} Connections. Contact an admin to create one.
|
||||
</p>
|
||||
)}
|
||||
<AddAppConnectionModal
|
||||
isOpen={popUp.addConnection.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
// remove form storage, not oauth connection
|
||||
localStorage.removeItem("secretRotationFormData");
|
||||
handlePopUpToggle("addConnection", isOpen);
|
||||
}}
|
||||
projectType={currentProject.type}
|
||||
projectId={currentProject.id}
|
||||
app={app}
|
||||
onComplete={(connection) => {
|
||||
if (connection) {
|
||||
setValue("connection", connection);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,8 +11,9 @@ import { SecretRotationV2ParametersFields } from "@app/components/secret-rotatio
|
||||
import { SecretRotationV2ReviewFields } from "@app/components/secret-rotations-v2/forms/SecretRotationV2ReviewFields";
|
||||
import { SecretRotationV2SecretsMappingFields } from "@app/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useProject } from "@app/context";
|
||||
import { IS_ROTATION_DUAL_CREDENTIALS, SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
|
||||
import { ProjectEnv } from "@app/hooks/api/projects/types";
|
||||
import {
|
||||
SecretRotation,
|
||||
TSecretRotationV2,
|
||||
@@ -22,7 +23,6 @@ import {
|
||||
useCreateSecretRotationV2,
|
||||
useUpdateSecretRotationV2
|
||||
} from "@app/hooks/api/secretRotationsV2/mutations";
|
||||
import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { SecretRotationV2FormSchema, TSecretRotationV2Form } from "./schemas";
|
||||
|
||||
@@ -32,8 +32,9 @@ type Props = {
|
||||
onCancel: () => void;
|
||||
secretPath: string;
|
||||
environment?: string;
|
||||
environments?: WorkspaceEnv[];
|
||||
environments?: ProjectEnv[];
|
||||
secretRotation?: TSecretRotationV2;
|
||||
initialFormData?: Partial<TSecretRotationV2Form>;
|
||||
};
|
||||
|
||||
const FORM_TABS: { name: string; key: string; fields: (keyof TSecretRotationV2Form)[] }[] = [
|
||||
@@ -64,11 +65,12 @@ export const SecretRotationV2Form = ({
|
||||
environment: envSlug,
|
||||
secretPath,
|
||||
secretRotation,
|
||||
environments
|
||||
environments,
|
||||
initialFormData
|
||||
}: Props) => {
|
||||
const createSecretRotation = useCreateSecretRotationV2();
|
||||
const updateSecretRotation = useUpdateSecretRotationV2();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentProject } = useProject();
|
||||
const { name: rotationType } = SECRET_ROTATION_MAP[type];
|
||||
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||
@@ -80,7 +82,7 @@ export const SecretRotationV2Form = ({
|
||||
defaultValues: secretRotation
|
||||
? {
|
||||
...secretRotation,
|
||||
environment: currentWorkspace?.environments.find((env) => env.slug === envSlug),
|
||||
environment: currentProject?.environments.find((env) => env.slug === envSlug),
|
||||
secretPath
|
||||
}
|
||||
: {
|
||||
@@ -91,9 +93,10 @@ export const SecretRotationV2Form = ({
|
||||
hours: 0,
|
||||
minutes: 0
|
||||
},
|
||||
environment: currentWorkspace?.environments.find((env) => env.slug === envSlug),
|
||||
environment: currentProject?.environments.find((env) => env.slug === envSlug),
|
||||
secretPath,
|
||||
...(rotationOption!.template as object) // can't infer type since we don't know which specific type it is
|
||||
...((rotationOption?.template as object) ?? {}), // can't infer type since we don't know which specific type it is
|
||||
...(initialFormData as object)
|
||||
},
|
||||
reValidateMode: "onChange"
|
||||
});
|
||||
@@ -115,7 +118,7 @@ export const SecretRotationV2Form = ({
|
||||
|
||||
connectionId: connection.id,
|
||||
environment: environment.slug,
|
||||
projectId: currentWorkspace.id
|
||||
projectId: currentProject.id
|
||||
});
|
||||
try {
|
||||
const rotation = await mutation;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
||||
|
||||
import { TSecretScanningDataSourceForm } from "@app/components/secret-scanning/forms/schemas";
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import {
|
||||
SecretScanningDataSource,
|
||||
@@ -21,16 +23,19 @@ type ContentProps = {
|
||||
onComplete: (dataSource: TSecretScanningDataSource) => void;
|
||||
selectedDataSource: SecretScanningDataSource | null;
|
||||
setSelectedDataSource: (selectedDataSource: SecretScanningDataSource | null) => void;
|
||||
initialFormData?: Partial<TSecretScanningDataSourceForm>;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
const Content = ({ setSelectedDataSource, selectedDataSource, ...props }: ContentProps) => {
|
||||
const Content = ({
|
||||
setSelectedDataSource,
|
||||
selectedDataSource,
|
||||
onCancel,
|
||||
...props
|
||||
}: ContentProps) => {
|
||||
if (selectedDataSource) {
|
||||
return (
|
||||
<SecretScanningDataSourceForm
|
||||
onCancel={() => setSelectedDataSource(null)}
|
||||
type={selectedDataSource}
|
||||
{...props}
|
||||
/>
|
||||
<SecretScanningDataSourceForm onCancel={onCancel} type={selectedDataSource} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,12 +46,60 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p
|
||||
const [selectedDataSource, setSelectedDataSource] = useState<SecretScanningDataSource | null>(
|
||||
null
|
||||
);
|
||||
const [initialFormData, setInitialFormData] = useState<Partial<TSecretScanningDataSourceForm>>();
|
||||
|
||||
const {
|
||||
location: {
|
||||
search: { connectionId, connectionName, ...search },
|
||||
pathname
|
||||
}
|
||||
} = useRouterState();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (connectionId && connectionName) {
|
||||
const storedFormData = localStorage.getItem("secretScanningDataSourceFormData");
|
||||
|
||||
if (!storedFormData) return;
|
||||
|
||||
let form: Partial<TSecretScanningDataSourceForm> = {};
|
||||
try {
|
||||
form = JSON.parse(storedFormData) as TSecretScanningDataSourceForm;
|
||||
} catch {
|
||||
return;
|
||||
} finally {
|
||||
localStorage.removeItem("secretScanningDataSourceFormData");
|
||||
}
|
||||
|
||||
onOpenChange(true);
|
||||
|
||||
setSelectedDataSource(form.type ?? null);
|
||||
|
||||
setInitialFormData({
|
||||
...form,
|
||||
connection: { id: connectionId, name: connectionName }
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: pathname,
|
||||
search
|
||||
});
|
||||
}
|
||||
}, [connectionId, connectionName]);
|
||||
|
||||
const resetModal = () => {
|
||||
setSelectedDataSource(null);
|
||||
setInitialFormData(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setSelectedDataSource(null);
|
||||
if (!open) {
|
||||
resetModal();
|
||||
}
|
||||
onOpenChange(open);
|
||||
}}
|
||||
>
|
||||
@@ -83,11 +136,13 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p
|
||||
>
|
||||
<Content
|
||||
onComplete={() => {
|
||||
setSelectedDataSource(null);
|
||||
resetModal();
|
||||
onOpenChange(false);
|
||||
}}
|
||||
onCancel={resetModal}
|
||||
selectedDataSource={selectedDataSource}
|
||||
setSelectedDataSource={setSelectedDataSource}
|
||||
initialFormData={initialFormData}
|
||||
{...props}
|
||||
/>
|
||||
</ModalContent>
|
||||
|
||||
+51
-21
@@ -1,14 +1,17 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { SingleValue } from "react-select";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
import { AppConnectionOption } from "@app/components/app-connections";
|
||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||
import { OrgPermissionSubjects, useOrgPermission } from "@app/context";
|
||||
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { ProjectPermissionSub, useProject, useProjectPermission } from "@app/context";
|
||||
import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||
import { SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP } from "@app/helpers/secretScanningV2";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useListAvailableAppConnections } from "@app/hooks/api/appConnections";
|
||||
import { AddAppConnectionModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components";
|
||||
|
||||
import { TSecretScanningDataSourceForm } from "./schemas";
|
||||
|
||||
@@ -21,19 +24,26 @@ export const SecretScanningDataSourceConnectionField = ({
|
||||
onChange: callback,
|
||||
isUpdate
|
||||
}: Props) => {
|
||||
const { permission } = useOrgPermission();
|
||||
const { control, watch } = useFormContext<TSecretScanningDataSourceForm>();
|
||||
const { permission } = useProjectPermission();
|
||||
const { control, watch, setValue } = useFormContext<TSecretScanningDataSourceForm>();
|
||||
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["addConnection"] as const);
|
||||
|
||||
const dataSourceType = watch("type");
|
||||
const app = SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP[dataSourceType];
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(app);
|
||||
const { currentProject } = useProject();
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(
|
||||
app,
|
||||
currentProject.id
|
||||
);
|
||||
|
||||
const connectionName = APP_CONNECTION_MAP[app].name;
|
||||
|
||||
const canCreateConnection = permission.can(
|
||||
OrgPermissionAppConnectionActions.Create,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -67,37 +77,57 @@ export const SecretScanningDataSourceConnectionField = ({
|
||||
<FilterableSelect
|
||||
value={value}
|
||||
onChange={(newValue) => {
|
||||
if ((newValue as SingleValue<{ id: string; name: string }>)?.id === "_create") {
|
||||
handlePopUpOpen("addConnection");
|
||||
onChange(null);
|
||||
// store for oauth callback connections
|
||||
localStorage.setItem("secretScanningDataSourceFormData", JSON.stringify(watch()));
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(newValue);
|
||||
if (callback) callback();
|
||||
}}
|
||||
isLoading={isPending}
|
||||
options={availableConnections}
|
||||
options={[
|
||||
...(canCreateConnection ? [{ id: "_create", name: "Create Connection" }] : []),
|
||||
...(availableConnections ?? [])
|
||||
]}
|
||||
isDisabled={isUpdate}
|
||||
placeholder="Select connection..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
components={{ Option: AppConnectionOption }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="connection"
|
||||
/>
|
||||
{!isUpdate && availableConnections?.length === 0 && (
|
||||
{!isUpdate && !isPending && !availableConnections?.length && !canCreateConnection && (
|
||||
<p className="-mt-2.5 mb-2.5 text-xs text-yellow">
|
||||
<FontAwesomeIcon className="mr-1" size="xs" icon={faInfoCircle} />
|
||||
{canCreateConnection ? (
|
||||
<>
|
||||
You do not have access to any {connectionName} Connections. Create one from the{" "}
|
||||
<Link to="/organization/app-connections" className="underline">
|
||||
App Connections
|
||||
</Link>{" "}
|
||||
page.
|
||||
</>
|
||||
) : (
|
||||
`You do not have access to any ${connectionName} Connections. Contact an admin to create one.`
|
||||
)}
|
||||
You do not have access to any {connectionName} Connections. Contact an admin to create
|
||||
one.
|
||||
</p>
|
||||
)}
|
||||
<AddAppConnectionModal
|
||||
isOpen={popUp.addConnection.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
// remove form storage, not oauth connection
|
||||
localStorage.removeItem("secretScanningDataSourceFormData");
|
||||
handlePopUpToggle("addConnection", isOpen);
|
||||
}}
|
||||
projectType={currentProject.type}
|
||||
projectId={currentProject.id}
|
||||
app={app}
|
||||
onComplete={(connection) => {
|
||||
if (connection) {
|
||||
setValue("connection", connection);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useProject } from "@app/context";
|
||||
import { SECRET_SCANNING_DATA_SOURCE_MAP } from "@app/helpers/secretScanningV2";
|
||||
import {
|
||||
SecretScanningDataSource,
|
||||
@@ -27,6 +27,7 @@ type Props = {
|
||||
type: SecretScanningDataSource;
|
||||
onCancel: () => void;
|
||||
dataSource?: TSecretScanningDataSource;
|
||||
initialFormData?: Partial<TSecretScanningDataSourceForm>;
|
||||
};
|
||||
|
||||
const FORM_TABS: { name: string; key: string; fields: (keyof TSecretScanningDataSourceForm)[] }[] =
|
||||
@@ -36,10 +37,16 @@ const FORM_TABS: { name: string; key: string; fields: (keyof TSecretScanningData
|
||||
{ name: "Review", key: "review", fields: [] }
|
||||
];
|
||||
|
||||
export const SecretScanningDataSourceForm = ({ type, onComplete, onCancel, dataSource }: Props) => {
|
||||
export const SecretScanningDataSourceForm = ({
|
||||
type,
|
||||
onComplete,
|
||||
onCancel,
|
||||
dataSource,
|
||||
initialFormData
|
||||
}: Props) => {
|
||||
const createDataSource = useCreateSecretScanningDataSource();
|
||||
const updateDataSource = useUpdateSecretScanningDataSource();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentProject } = useProject();
|
||||
const { name: sourceType } = SECRET_SCANNING_DATA_SOURCE_MAP[type];
|
||||
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||
@@ -48,7 +55,8 @@ export const SecretScanningDataSourceForm = ({ type, onComplete, onCancel, dataS
|
||||
resolver: zodResolver(SecretScanningDataSourceSchema),
|
||||
defaultValues: dataSource ?? {
|
||||
type,
|
||||
isAutoScanEnabled: true // scott: this may need to be derived from type in the future
|
||||
isAutoScanEnabled: true, // scott: this may need to be derived from type in the future
|
||||
...(initialFormData as object)
|
||||
},
|
||||
reValidateMode: "onChange"
|
||||
});
|
||||
@@ -63,7 +71,7 @@ export const SecretScanningDataSourceForm = ({ type, onComplete, onCancel, dataS
|
||||
: createDataSource.mutateAsync({
|
||||
...formData,
|
||||
connectionId: connection?.id,
|
||||
projectId: currentWorkspace.id
|
||||
projectId: currentProject.id
|
||||
});
|
||||
try {
|
||||
const source = await mutation;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
@@ -11,18 +12,21 @@ type Props = {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
selectSync?: SecretSync | null;
|
||||
initialFormData?: Partial<TSecretSyncForm>;
|
||||
};
|
||||
|
||||
type ContentProps = {
|
||||
onComplete: (secretSync: TSecretSync) => void;
|
||||
selectedSync: SecretSync | null;
|
||||
setSelectedSync: (selectedSync: SecretSync | null) => void;
|
||||
initialFormData?: Partial<TSecretSyncForm>;
|
||||
};
|
||||
|
||||
const Content = ({ onComplete, setSelectedSync, selectedSync }: ContentProps) => {
|
||||
const Content = ({ onComplete, setSelectedSync, selectedSync, initialFormData }: ContentProps) => {
|
||||
if (selectedSync) {
|
||||
return (
|
||||
<CreateSecretSyncForm
|
||||
initialFormData={initialFormData}
|
||||
onComplete={onComplete}
|
||||
onCancel={() => setSelectedSync(null)}
|
||||
destination={selectedSync}
|
||||
@@ -33,7 +37,12 @@ const Content = ({ onComplete, setSelectedSync, selectedSync }: ContentProps) =>
|
||||
return <SecretSyncSelect onSelect={setSelectedSync} />;
|
||||
};
|
||||
|
||||
export const CreateSecretSyncModal = ({ onOpenChange, selectSync = null, ...props }: Props) => {
|
||||
export const CreateSecretSyncModal = ({
|
||||
onOpenChange,
|
||||
selectSync = null,
|
||||
initialFormData,
|
||||
...props
|
||||
}: Props) => {
|
||||
const [selectedSync, setSelectedSync] = useState<SecretSync | null>(selectSync);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -67,6 +76,7 @@ export const CreateSecretSyncModal = ({ onOpenChange, selectSync = null, ...prop
|
||||
}}
|
||||
selectedSync={selectedSync}
|
||||
setSelectedSync={setSelectedSync}
|
||||
initialFormData={initialFormData}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
faCheck,
|
||||
faExclamationTriangle,
|
||||
faHourglass,
|
||||
faRotate,
|
||||
IconDefinition
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -29,7 +30,11 @@ export const SecretSyncStatusBadge = ({ status }: Props) => {
|
||||
text = "Synced";
|
||||
icon = faCheck;
|
||||
break;
|
||||
case SecretSyncStatus.Pending: // no need to differentiate from user perspective
|
||||
case SecretSyncStatus.Pending:
|
||||
variant = "primary";
|
||||
text = "Queued";
|
||||
icon = faHourglass;
|
||||
break;
|
||||
case SecretSyncStatus.Running:
|
||||
default:
|
||||
variant = "primary";
|
||||
@@ -42,11 +47,7 @@ export const SecretSyncStatusBadge = ({ status }: Props) => {
|
||||
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap" variant={variant}>
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className={
|
||||
[SecretSyncStatus.Pending, SecretSyncStatus.Running].includes(status)
|
||||
? "animate-spin"
|
||||
: ""
|
||||
}
|
||||
className={[SecretSyncStatus.Running].includes(status) ? "animate-spin" : ""}
|
||||
/>
|
||||
<span>{text}</span>
|
||||
</Badge>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Switch } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useProject } from "@app/context";
|
||||
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
||||
import {
|
||||
SecretSync,
|
||||
@@ -29,6 +29,7 @@ type Props = {
|
||||
onComplete: (secretSync: TSecretSync) => void;
|
||||
destination: SecretSync;
|
||||
onCancel: () => void;
|
||||
initialFormData?: Partial<TSecretSyncForm>;
|
||||
};
|
||||
|
||||
const FORM_TABS: { name: string; key: string; fields: (keyof TSecretSyncForm)[] }[] = [
|
||||
@@ -39,14 +40,20 @@ const FORM_TABS: { name: string; key: string; fields: (keyof TSecretSyncForm)[]
|
||||
{ name: "Review", key: "review", fields: [] }
|
||||
];
|
||||
|
||||
export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Props) => {
|
||||
export const CreateSecretSyncForm = ({
|
||||
destination,
|
||||
onComplete,
|
||||
onCancel,
|
||||
initialFormData
|
||||
}: Props) => {
|
||||
const createSecretSync = useCreateSecretSync();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentProject } = useProject();
|
||||
const { name: destinationName } = SECRET_SYNC_MAP[destination];
|
||||
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||
// scoot: right now we only do this when creating a connection so we know index 1
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(initialFormData ? 1 : 0);
|
||||
|
||||
const { syncOption } = useSecretSyncOption(destination);
|
||||
|
||||
@@ -59,7 +66,8 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
||||
initialSyncBehavior: syncOption?.canImportSecrets
|
||||
? undefined
|
||||
: SecretSyncInitialSyncBehavior.OverwriteDestination
|
||||
}
|
||||
},
|
||||
...initialFormData
|
||||
} as Partial<TSecretSyncForm>,
|
||||
reValidateMode: "onChange"
|
||||
});
|
||||
@@ -70,7 +78,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
||||
...formData,
|
||||
connectionId: connection.id,
|
||||
environment: environment.slug,
|
||||
projectId: currentWorkspace.id
|
||||
projectId: currentProject.id
|
||||
});
|
||||
|
||||
createNotification({
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { SingleValue } from "react-select";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
import { AppConnectionOption } from "@app/components/app-connections";
|
||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||
import { OrgPermissionSubjects, useOrgPermission } from "@app/context";
|
||||
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { ProjectPermissionSub, useProject, useProjectPermission } from "@app/context";
|
||||
import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||
import { SECRET_SYNC_CONNECTION_MAP } from "@app/helpers/secretSyncs";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useListAvailableAppConnections } from "@app/hooks/api/appConnections";
|
||||
import { AddAppConnectionModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components";
|
||||
|
||||
import { TSecretSyncForm } from "./schemas";
|
||||
|
||||
@@ -17,19 +20,26 @@ type Props = {
|
||||
};
|
||||
|
||||
export const SecretSyncConnectionField = ({ onChange: callback }: Props) => {
|
||||
const { permission } = useOrgPermission();
|
||||
const { control, watch } = useFormContext<TSecretSyncForm>();
|
||||
const { permission } = useProjectPermission();
|
||||
const { control, watch, setValue } = useFormContext<TSecretSyncForm>();
|
||||
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["addConnection"] as const);
|
||||
|
||||
const destination = watch("destination");
|
||||
const app = SECRET_SYNC_CONNECTION_MAP[destination];
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(app);
|
||||
const { currentProject } = useProject();
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(
|
||||
app,
|
||||
currentProject.id
|
||||
);
|
||||
|
||||
const connectionName = APP_CONNECTION_MAP[app].name;
|
||||
|
||||
const canCreateConnection = permission.can(
|
||||
OrgPermissionAppConnectionActions.Create,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
const appName = APP_CONNECTION_MAP[SECRET_SYNC_CONNECTION_MAP[destination]].name;
|
||||
@@ -51,36 +61,55 @@ export const SecretSyncConnectionField = ({ onChange: callback }: Props) => {
|
||||
<FilterableSelect
|
||||
value={value}
|
||||
onChange={(newValue) => {
|
||||
if ((newValue as SingleValue<{ id: string; name: string }>)?.id === "_create") {
|
||||
handlePopUpOpen("addConnection");
|
||||
onChange(null);
|
||||
// store for oauth callback connections
|
||||
localStorage.setItem("secretSyncFormData", JSON.stringify(watch()));
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(newValue);
|
||||
if (callback) callback();
|
||||
}}
|
||||
isLoading={isPending}
|
||||
options={availableConnections}
|
||||
options={[
|
||||
...(canCreateConnection ? [{ id: "_create", name: "Create Connection" }] : []),
|
||||
...(availableConnections ?? [])
|
||||
]}
|
||||
placeholder="Select connection..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
components={{ Option: AppConnectionOption }}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="connection"
|
||||
/>
|
||||
{availableConnections?.length === 0 && (
|
||||
{!isPending && !availableConnections?.length && !canCreateConnection && (
|
||||
<p className="-mt-2.5 mb-2.5 text-xs text-yellow">
|
||||
<FontAwesomeIcon className="mr-1" size="xs" icon={faInfoCircle} />
|
||||
{canCreateConnection ? (
|
||||
<>
|
||||
You do not have access to any {appName} Connections. Create one from the{" "}
|
||||
<Link to="/organization/app-connections" className="underline">
|
||||
App Connections
|
||||
</Link>{" "}
|
||||
page.
|
||||
</>
|
||||
) : (
|
||||
`You do not have access to any ${appName} Connections. Contact an admin to create one.`
|
||||
)}
|
||||
You do not have access to any {appName} Connections. Contact an admin to create one.
|
||||
</p>
|
||||
)}
|
||||
<AddAppConnectionModal
|
||||
isOpen={popUp.addConnection.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
// remove form storage, not oauth connection
|
||||
localStorage.removeItem("secretSyncFormData");
|
||||
handlePopUpToggle("addConnection", isOpen);
|
||||
}}
|
||||
projectType={currentProject.type}
|
||||
projectId={currentProject.id}
|
||||
app={app}
|
||||
onComplete={(connection) => {
|
||||
if (connection) {
|
||||
setValue("connection", connection);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { subject } from "@casl/ability";
|
||||
|
||||
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { useProjectPermission, useWorkspace } from "@app/context";
|
||||
import { useProject, useProjectPermission } from "@app/context";
|
||||
import {
|
||||
ProjectPermissionSecretSyncActions,
|
||||
ProjectPermissionSub
|
||||
@@ -16,7 +16,7 @@ export const SecretSyncSourceFields = () => {
|
||||
const { control, watch, setError, clearErrors } = useFormContext<TSecretSyncForm>();
|
||||
|
||||
const { permission } = useProjectPermission();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentProject } = useProject();
|
||||
|
||||
const selectedEnvironment = watch("environment");
|
||||
const selectedSecretPath = watch("secretPath");
|
||||
@@ -48,7 +48,7 @@ export const SecretSyncSourceFields = () => {
|
||||
</p>
|
||||
|
||||
<Controller
|
||||
defaultValue={currentWorkspace.environments[0]}
|
||||
defaultValue={currentProject.environments[0]}
|
||||
control={control}
|
||||
name="environment"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
@@ -56,7 +56,7 @@ export const SecretSyncSourceFields = () => {
|
||||
<FilterableSelect
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
options={currentWorkspace.environments}
|
||||
options={currentProject.environments}
|
||||
placeholder="Select environment..."
|
||||
getOptionLabel={(option) => option?.name}
|
||||
getOptionValue={(option) => option?.id}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { FormControl, FormLabel, SecretInput, Spinner, Tooltip } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useProject } from "@app/context";
|
||||
import { useGetSecretReferenceTree } from "@app/hooks/api";
|
||||
import { ApiErrorTypes, TApiErrors, TSecretReferenceTraceNode } from "@app/hooks/api/types";
|
||||
|
||||
@@ -89,8 +89,8 @@ export const SecretReferenceNode = ({
|
||||
};
|
||||
|
||||
export const SecretReferenceTree = ({ secretPath, environment, secretKey }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const projectId = currentWorkspace?.id || "";
|
||||
const { currentProject } = useProject();
|
||||
const projectId = currentProject?.id || "";
|
||||
|
||||
const { data, isPending, isError, error } = useGetSecretReferenceTree({
|
||||
secretPath,
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
ModalContent,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useProject } from "@app/context";
|
||||
import { useCreateWsTag } from "@app/hooks/api";
|
||||
import { slugSchema } from "@app/lib/schemas";
|
||||
|
||||
@@ -115,11 +115,10 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
||||
}
|
||||
});
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const { currentProject } = useProject();
|
||||
const projectId = currentProject?.id || "";
|
||||
|
||||
const { mutateAsync: createWsTag } = useCreateWsTag();
|
||||
|
||||
const [showHexInput, setShowHexInput] = useState<boolean>(false);
|
||||
const selectedTagColor = watch("color", secretTagsColors[0].hex);
|
||||
|
||||
@@ -130,7 +129,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
||||
const onFormSubmit = async ({ slug, color }: FormData) => {
|
||||
try {
|
||||
await createWsTag({
|
||||
workspaceID: workspaceId,
|
||||
projectId,
|
||||
tagColor: color,
|
||||
tagSlug: slug
|
||||
});
|
||||
|
||||
@@ -11,9 +11,10 @@ type Props = {
|
||||
text?: string | string[];
|
||||
frequency?: number;
|
||||
className?: string;
|
||||
lottieClassName?: string;
|
||||
};
|
||||
|
||||
export const ContentLoader = ({ text, frequency = 2000, className }: Props) => {
|
||||
export const ContentLoader = ({ text, frequency = 2000, className, lottieClassName }: Props) => {
|
||||
const [pos, setPos] = useState(0);
|
||||
const isTextArray = Array.isArray(text);
|
||||
useEffect(() => {
|
||||
@@ -33,7 +34,11 @@ export const ContentLoader = ({ text, frequency = 2000, className }: Props) => {
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Lottie isAutoPlay icon="infisical_loading" className="h-32 w-32" />
|
||||
<Lottie
|
||||
isAutoPlay
|
||||
icon="infisical_loading"
|
||||
className={twMerge("h-32 w-32", lottieClassName)}
|
||||
/>
|
||||
{text && isTextArray && (
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
|
||||
@@ -60,7 +60,8 @@ export const CreatableSelect = <T,>({
|
||||
isSelected && "text-mineshaft-200",
|
||||
"px-3 py-2 text-xs hover:cursor-pointer"
|
||||
),
|
||||
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md"
|
||||
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md",
|
||||
loadingMessage: () => "text-mineshaft-400 p-2 rounded-md"
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -91,7 +91,7 @@ export const FilterableSelect = <T,>({
|
||||
),
|
||||
placeholder: () =>
|
||||
`${isMulti ? "py-[0.22rem]" : "leading-7"} text-mineshaft-400 text-sm pl-1`,
|
||||
input: () => "pl-1",
|
||||
input: () => `pl-1 ${isMulti ? "py-[0.22rem]" : ""}`,
|
||||
valueContainer: () =>
|
||||
`px-1 max-h-[8.2rem] ${
|
||||
isMulti ? "!overflow-y-auto thin-scrollbar py-1" : "py-[0.1rem]"
|
||||
@@ -114,7 +114,8 @@ export const FilterableSelect = <T,>({
|
||||
isSelected && "text-mineshaft-200",
|
||||
"rounded px-3 py-2 text-xs hover:cursor-pointer"
|
||||
),
|
||||
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md"
|
||||
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md",
|
||||
loadingMessage: () => "text-mineshaft-400 p-2 rounded-md"
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { faFolder, faKey, faLayerGroup } from "@fortawesome/free-solid-svg-icons
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useProject } from "@app/context";
|
||||
import { useDebounce, useToggle } from "@app/hooks";
|
||||
import { useGetProjectFolders, useGetProjectSecrets } from "@app/hooks/api";
|
||||
|
||||
@@ -76,8 +76,8 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const { currentProject } = useProject();
|
||||
const projectId = currentProject?.id || "";
|
||||
|
||||
const [debouncedValue] = useDebounce(value, 100);
|
||||
|
||||
@@ -101,7 +101,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
let predicate = suggestionSourceValue;
|
||||
if (isDeep) {
|
||||
const [envSlug, ...folderPaths] = suggestionSourceValue.split(".");
|
||||
const isValidEnvSlug = currentWorkspace?.environments.find((e) => e.slug === envSlug);
|
||||
const isValidEnvSlug = currentProject?.environments.find((e) => e.slug === envSlug);
|
||||
suggestionSourceEnv = isValidEnvSlug ? envSlug : undefined;
|
||||
suggestionSourceSecretPath = `/${folderPaths.slice(0, -1)?.join("/")}`;
|
||||
predicate = folderPaths[folderPaths.length - 1];
|
||||
@@ -125,7 +125,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
viewSecretValue: false,
|
||||
environment: suggestionSource.environment || "",
|
||||
secretPath: suggestionSource.secretPath || "",
|
||||
workspaceId,
|
||||
projectId,
|
||||
options: {
|
||||
enabled: isPopupOpen
|
||||
}
|
||||
@@ -133,7 +133,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
const { data: folders } = useGetProjectFolders({
|
||||
environment: suggestionSource.environment || "",
|
||||
path: suggestionSource.secretPath || "",
|
||||
projectId: workspaceId,
|
||||
projectId,
|
||||
options: {
|
||||
enabled: isPopupOpen
|
||||
}
|
||||
@@ -148,7 +148,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
|
||||
if (!suggestionSource.isDeep) {
|
||||
// At first level only environments and secrets
|
||||
(currentWorkspace?.environments || []).forEach(({ name, slug }) => {
|
||||
(currentProject?.environments || []).forEach(({ name, slug }) => {
|
||||
if (name.toLowerCase().startsWith(predicate))
|
||||
suggestionsArr.push({
|
||||
label: name,
|
||||
@@ -176,7 +176,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
});
|
||||
});
|
||||
return suggestionsArr;
|
||||
}, [secrets, folders, currentWorkspace?.environments, isPopupOpen, suggestionSource.value]);
|
||||
}, [secrets, folders, currentProject?.environments, isPopupOpen, suggestionSource.value]);
|
||||
|
||||
const handleSuggestionSelect = (selectIndex?: number) => {
|
||||
const selectedSuggestion =
|
||||
|
||||
@@ -34,7 +34,9 @@ const syntaxHighlight = (content?: string | null, isVisible?: boolean, isImport?
|
||||
|
||||
// akhilmhdh: Dont remove this br. I am still clueless how this works but weirdly enough
|
||||
// when break is added a line break works properly
|
||||
return formattedContent.concat(<br key={`secret-value-${formattedContent.length + 1}`} />);
|
||||
return formattedContent.concat(
|
||||
<br key={`secret-value-linebreak-${formattedContent.length + 1}`} />
|
||||
);
|
||||
};
|
||||
|
||||
type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useProject } from "@app/context";
|
||||
import { useDebounce } from "@app/hooks";
|
||||
import { useGetFoldersByEnv } from "@app/hooks/api";
|
||||
|
||||
@@ -35,12 +35,12 @@ export const SecretPathInput = ({
|
||||
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
||||
const [debouncedInputValue] = useDebounce(inputValue, 200);
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const { currentProject } = useProject();
|
||||
const projectId = currentProject?.id || "";
|
||||
const { folderNames: folders } = useGetFoldersByEnv({
|
||||
path: secretPath,
|
||||
environments: [environment || currentWorkspace?.environments?.[0].slug || ""],
|
||||
projectId: workspaceId
|
||||
environments: [environment || currentProject?.environments?.[0].slug || ""],
|
||||
projectId
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -16,6 +16,7 @@ export type TooltipProps = Omit<TooltipPrimitive.TooltipContentProps, "open" | "
|
||||
center?: boolean;
|
||||
size?: "sm" | "md";
|
||||
rootProps?: RootProps;
|
||||
delayDuration?: number;
|
||||
};
|
||||
|
||||
export const Tooltip = ({
|
||||
@@ -31,12 +32,13 @@ export const Tooltip = ({
|
||||
position = "top",
|
||||
size = "md",
|
||||
rootProps,
|
||||
delayDuration = 50,
|
||||
...props
|
||||
}: TooltipProps) =>
|
||||
// just render children if tooltip content is empty
|
||||
content ? (
|
||||
<TooltipPrimitive.Root
|
||||
delayDuration={50}
|
||||
delayDuration={delayDuration}
|
||||
{...rootProps}
|
||||
open={isOpen}
|
||||
defaultOpen={defaultOpen}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MongoAbility } from "@casl/ability";
|
||||
import { ForcedSubject, MongoAbility } from "@casl/ability";
|
||||
|
||||
export enum OrgPermissionActions {
|
||||
Read = "read",
|
||||
@@ -32,6 +32,7 @@ export enum OrgPermissionMachineIdentityAuthTemplateActions {
|
||||
|
||||
export enum OrgPermissionSubjects {
|
||||
Workspace = "workspace",
|
||||
Project = "project",
|
||||
Role = "role",
|
||||
Member = "member",
|
||||
Settings = "settings",
|
||||
@@ -109,6 +110,7 @@ export type AppConnectionSubjectFields = {
|
||||
|
||||
export type OrgPermissionSet =
|
||||
| [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace]
|
||||
| [OrgPermissionActions.Create, OrgPermissionSubjects.Project]
|
||||
| [OrgPermissionActions.Read, OrgPermissionSubjects.Workspace]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Role]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Member]
|
||||
@@ -126,7 +128,6 @@ export type OrgPermissionSet =
|
||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
||||
| [OrgPermissionAuditLogsActions, OrgPermissionSubjects.AuditLogs]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||
| [OrgPermissionAppConnectionActions, OrgPermissionSubjects.AppConnections]
|
||||
| [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity]
|
||||
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
|
||||
| [
|
||||
@@ -134,14 +135,13 @@ export type OrgPermissionSet =
|
||||
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||
]
|
||||
| [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway]
|
||||
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
|
||||
// TODO(scott): add back once org UI refactored
|
||||
// | [
|
||||
// OrgPermissionAppConnectionActions,
|
||||
// (
|
||||
// | OrgPermissionSubjects.AppConnections
|
||||
// | (ForcedSubject<OrgPermissionSubjects.AppConnections> & AppConnectionSubjectFields)
|
||||
// )
|
||||
// ];
|
||||
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare]
|
||||
| [
|
||||
OrgPermissionAppConnectionActions,
|
||||
(
|
||||
| OrgPermissionSubjects.AppConnections
|
||||
| (ForcedSubject<OrgPermissionSubjects.AppConnections> & AppConnectionSubjectFields)
|
||||
)
|
||||
];
|
||||
|
||||
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
|
||||
import { projectKeys } from "@app/hooks/api";
|
||||
import { fetchProjectById } from "@app/hooks/api/projects/queries";
|
||||
|
||||
export const useProject = () => {
|
||||
const params = useParams({
|
||||
strict: false
|
||||
});
|
||||
if (!params.projectId) {
|
||||
throw new Error("Missing project id");
|
||||
}
|
||||
|
||||
const { data: currentProject } = useSuspenseQuery({
|
||||
queryKey: projectKeys.getProjectById(params.projectId),
|
||||
queryFn: () => fetchProjectById(params.projectId as string),
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
return { currentProject, projectId: currentProject.id };
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { useProject } from "./ProjectContext";
|
||||
@@ -22,8 +22,8 @@ export const useProjectPermission = () => {
|
||||
const {
|
||||
data: { permission, membership, assumedPrivilegeDetails }
|
||||
} = useSuspenseQuery({
|
||||
queryKey: roleQueryKeys.getUserProjectPermissions({ workspaceId: projectId }),
|
||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: projectId }),
|
||||
queryKey: roleQueryKeys.getUserProjectPermissions({ projectId }),
|
||||
queryFn: () => fetchUserProjectPermissions({ projectId }),
|
||||
staleTime: Infinity,
|
||||
select: (data) => {
|
||||
const rule = unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(data.permissions);
|
||||
|
||||
@@ -154,6 +154,14 @@ export enum ProjectPermissionAuditLogsActions {
|
||||
Read = "read"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionAppConnectionActions {
|
||||
Read = "read-app-connections",
|
||||
Create = "create-app-connections",
|
||||
Edit = "edit-app-connections",
|
||||
Delete = "delete-app-connections",
|
||||
Connect = "connect-app-connections"
|
||||
}
|
||||
|
||||
export enum PermissionConditionOperators {
|
||||
$IN = "$in",
|
||||
$ALL = "$all",
|
||||
@@ -173,6 +181,10 @@ export type IdentityManagementSubjectFields = {
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type AppConnectionSubjectFields = {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export type ConditionalProjectPermissionSubject =
|
||||
| ProjectPermissionSub.SecretSyncs
|
||||
| ProjectPermissionSub.Secrets
|
||||
@@ -184,7 +196,8 @@ export type ConditionalProjectPermissionSubject =
|
||||
| ProjectPermissionSub.SecretFolders
|
||||
| ProjectPermissionSub.SecretImports
|
||||
| ProjectPermissionSub.SecretRotation
|
||||
| ProjectPermissionSub.SecretEvents;
|
||||
| ProjectPermissionSub.SecretEvents
|
||||
| ProjectPermissionSub.AppConnections;
|
||||
|
||||
export const formatedConditionsOperatorNames: { [K in PermissionConditionOperators]: string } = {
|
||||
[PermissionConditionOperators.$EQ]: "equal to",
|
||||
@@ -263,7 +276,8 @@ export enum ProjectPermissionSub {
|
||||
SecretScanningDataSources = "secret-scanning-data-sources",
|
||||
SecretScanningFindings = "secret-scanning-findings",
|
||||
SecretScanningConfigs = "secret-scanning-configs",
|
||||
SecretEvents = "secret-events"
|
||||
SecretEvents = "secret-events",
|
||||
AppConnections = "app-connections"
|
||||
}
|
||||
|
||||
export type SecretSubjectFields = {
|
||||
@@ -431,6 +445,13 @@ export type ProjectPermissionSet =
|
||||
| ProjectPermissionSub.SecretEvents
|
||||
| (ForcedSubject<ProjectPermissionSub.SecretEvents> & SecretEventSubjectFields)
|
||||
)
|
||||
]
|
||||
| [
|
||||
ProjectPermissionAppConnectionActions,
|
||||
(
|
||||
| ProjectPermissionSub.AppConnections
|
||||
| (ForcedSubject<ProjectPermissionSub.AppConnections> & AppConnectionSubjectFields)
|
||||
)
|
||||
];
|
||||
|
||||
export type TProjectPermission = MongoAbility<ProjectPermissionSet>;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useRouteContext } from "@tanstack/react-router";
|
||||
|
||||
import { fetchOrgSubscription, subscriptionQueryKeys } from "@app/hooks/api/subscriptions/queries";
|
||||
|
||||
export const useSubscription = () => {
|
||||
export const useSubscription = (refreshCache?: boolean) => {
|
||||
const organizationId = useRouteContext({
|
||||
from: "/_authenticate/_inject-org-details",
|
||||
select: (el) => el.organizationId
|
||||
@@ -11,7 +11,7 @@ export const useSubscription = () => {
|
||||
|
||||
const { data: subscription } = useSuspenseQuery({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId),
|
||||
queryFn: () => fetchOrgSubscription(organizationId),
|
||||
queryFn: () => fetchOrgSubscription(organizationId, refreshCache),
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
|
||||
import { workspaceKeys } from "@app/hooks/api";
|
||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||
|
||||
export const useWorkspace = () => {
|
||||
const params = useParams({
|
||||
strict: false
|
||||
});
|
||||
if (!params.projectId) {
|
||||
throw new Error("Missing project id");
|
||||
}
|
||||
|
||||
const { data: currentWorkspace } = useSuspenseQuery({
|
||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||
queryFn: () => fetchWorkspaceById(params.projectId as string),
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
return { currentWorkspace };
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { useWorkspace } from "./WorkspaceContext";
|
||||
@@ -9,6 +9,7 @@ export {
|
||||
OrgPermissionSubjects,
|
||||
useOrgPermission
|
||||
} from "./OrgPermissionContext";
|
||||
export { useProject } from "./ProjectContext";
|
||||
export type { TProjectPermission } from "./ProjectPermissionContext";
|
||||
export {
|
||||
ProjectPermissionActions,
|
||||
@@ -29,4 +30,3 @@ export {
|
||||
export { useServerConfig } from "./ServerConfigContext";
|
||||
export { useSubscription } from "./SubscriptionContext";
|
||||
export { useUser } from "./UserContext";
|
||||
export { useWorkspace } from "./WorkspaceContext";
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
faServer,
|
||||
faUser
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { useRouterState } from "@tanstack/react-router";
|
||||
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import {
|
||||
@@ -221,3 +222,11 @@ export const AWS_REGIONS = [
|
||||
{ name: "AWS GovCloud (US-East)", slug: "us-gov-east-1" },
|
||||
{ name: "AWS GovCloud (US-West)", slug: "us-gov-west-1" }
|
||||
];
|
||||
|
||||
export const useGetAppConnectionOauthReturnUrl = () => {
|
||||
const {
|
||||
location: { pathname }
|
||||
} = useRouterState();
|
||||
|
||||
return pathname;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { faCode, IconDefinition } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { LogProvider } from "@app/hooks/api/auditLogStreams/enums";
|
||||
import { TAuditLogStream } from "@app/hooks/api/types";
|
||||
import { DiscriminativePick } from "@app/types";
|
||||
|
||||
export const AUDIT_LOG_STREAM_PROVIDER_MAP: Record<
|
||||
LogProvider,
|
||||
{ name: string; image?: string; icon?: IconDefinition; size?: number }
|
||||
> = {
|
||||
[LogProvider.Azure]: { name: "Azure", image: "Microsoft Azure.png", size: 60 },
|
||||
[LogProvider.Cribl]: { name: "Cribl", image: "Cribl.png", size: 60 },
|
||||
[LogProvider.Custom]: { name: "Custom", icon: faCode },
|
||||
[LogProvider.Datadog]: { name: "Datadog", image: "Datadog.png" },
|
||||
[LogProvider.Splunk]: { name: "Splunk", image: "Splunk.png", size: 65 }
|
||||
};
|
||||
|
||||
// Strictly for showing to the client in the front-end
|
||||
export function getProviderUrl(
|
||||
logStream: DiscriminativePick<TAuditLogStream, "provider" | "credentials">
|
||||
) {
|
||||
switch (logStream.provider) {
|
||||
case LogProvider.Custom:
|
||||
case LogProvider.Datadog:
|
||||
case LogProvider.Cribl:
|
||||
return logStream.credentials.url;
|
||||
case LogProvider.Splunk:
|
||||
return `https://${logStream.credentials.hostname}:8088/services/collector/event`;
|
||||
case LogProvider.Azure:
|
||||
return `${logStream.credentials.dceUrl}/dataCollectionRules/${logStream.credentials.dcrId}/streams/Custom-${logStream.credentials.cltName}_CL`;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled provider in getProviderUrl: ${(logStream as TAuditLogStream).provider}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,3 +4,15 @@ export const downloadTxtFile = (filename: string, content: string) => {
|
||||
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||||
FileSaver.saveAs(blob, filename);
|
||||
};
|
||||
|
||||
export const downloadFile = (content: string, filename: string, mimeType: string = "text/csv") => {
|
||||
const blob = new Blob([content], { type: mimeType });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { createWorkspace } from "@app/hooks/api/workspace/queries";
|
||||
import { ProjectType, WorkspaceEnv } from "@app/hooks/api/workspace/types";
|
||||
import { createWorkspace } from "@app/hooks/api/projects/queries";
|
||||
import { ProjectEnv, ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
const secretsToBeAdded = [
|
||||
{
|
||||
@@ -47,8 +47,8 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
|
||||
});
|
||||
|
||||
try {
|
||||
const { data } = await apiRequest.post("/api/v3/secrets/batch/raw", {
|
||||
workspaceId: project.id,
|
||||
const { data } = await apiRequest.post("/api/v4/secrets/batch", {
|
||||
projectId: project.id,
|
||||
environment: "dev",
|
||||
secretPath: "/",
|
||||
secrets: secretsToBeAdded
|
||||
@@ -74,7 +74,7 @@ export const getProjectBaseURL = (type: ProjectType) => {
|
||||
|
||||
// @ts-expect-error akhilmhdh: will remove this later
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const getProjectHomePage = (type: ProjectType, environments: WorkspaceEnv[]) => {
|
||||
export const getProjectHomePage = (type: ProjectType, environments: ProjectEnv[]) => {
|
||||
switch (type) {
|
||||
case ProjectType.SecretManager:
|
||||
return "/projects/secret-management/$projectId/overview" as const;
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
export const accessApprovalKeys = {
|
||||
getAccessApprovalPolicies: (projectSlug: string) =>
|
||||
[{ projectSlug }, "access-approval-policies"] as const,
|
||||
getAccessApprovalPolicyOfABoard: (workspaceId: string, environment: string) =>
|
||||
[{ workspaceId, environment }, "access-approval-policy"] as const,
|
||||
getAccessApprovalPolicyOfABoard: (projectId: string, environment: string) =>
|
||||
[{ projectId, environment }, "access-approval-policy"] as const,
|
||||
|
||||
getAccessApprovalRequests: (
|
||||
projectSlug: string,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { EnforcementLevel, PolicyType } from "../policies/enums";
|
||||
import { ProjectEnv } from "../projects/types";
|
||||
import { TProjectPermission } from "../roles/types";
|
||||
import { ApprovalStatus } from "../secretApprovalRequest/types";
|
||||
import { WorkspaceEnv } from "../workspace/types";
|
||||
|
||||
export type TAccessApprovalPolicy = {
|
||||
id: string;
|
||||
@@ -9,7 +9,7 @@ export type TAccessApprovalPolicy = {
|
||||
approvals: number;
|
||||
secretPath: string;
|
||||
workspace: string;
|
||||
environments: WorkspaceEnv[];
|
||||
environments: ProjectEnv[];
|
||||
projectId: string;
|
||||
policyType: PolicyType;
|
||||
approversRequired: boolean;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { Organization } from "@app/hooks/api/organization/types";
|
||||
|
||||
import { organizationKeys } from "../organization/queries";
|
||||
import { User } from "../users/types";
|
||||
@@ -8,9 +9,12 @@ import { adminQueryKeys, adminStandaloneKeys } from "./queries";
|
||||
import {
|
||||
RootKeyEncryptionStrategy,
|
||||
TCreateAdminUserDTO,
|
||||
TCreateOrganizationDTO,
|
||||
TInvalidateCacheDTO,
|
||||
TResendOrgInviteDTO,
|
||||
TServerConfig,
|
||||
TUpdateServerConfigDTO
|
||||
TUpdateServerConfigDTO,
|
||||
TUsageReportResponse
|
||||
} from "./types";
|
||||
|
||||
export const useCreateAdminUser = () => {
|
||||
@@ -193,3 +197,58 @@ export const useInvalidateCache = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useServerAdminCreateOrganization = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (opt: TCreateOrganizationDTO) => {
|
||||
const { data } = await apiRequest.post<{ organization: Organization }>(
|
||||
"/api/v1/admin/organization-management/organizations",
|
||||
opt
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: adminQueryKeys.getOrganizations() });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useServerAdminResendOrgInvite = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ organizationId, membershipId }: TResendOrgInviteDTO) => {
|
||||
await apiRequest.post(
|
||||
`/api/v1/admin/organization-management/organizations/${organizationId}/memberships/${membershipId}/resend-invite`
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useServerAdminAccessOrg = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (orgId: string) => {
|
||||
const { data } = await apiRequest.post(
|
||||
`/api/v1/admin/organization-management/organizations/${orgId}/access`
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: organizationKeys.getUserOrganizations });
|
||||
queryClient.invalidateQueries({ queryKey: adminQueryKeys.getOrganizations() });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useGenerateUsageReport = () => {
|
||||
return useMutation<TUsageReportResponse, object, void>({
|
||||
mutationFn: async () => {
|
||||
const { data } = await apiRequest.post<TUsageReportResponse>(
|
||||
"/api/v1/admin/usage-report/generate"
|
||||
);
|
||||
return data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { useInfiniteQuery, useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||
import {
|
||||
DefaultError,
|
||||
InfiniteData,
|
||||
UndefinedInitialDataInfiniteOptions,
|
||||
useInfiniteQuery,
|
||||
useQuery,
|
||||
UseQueryOptions
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { Identity } from "@app/hooks/api/identities/types";
|
||||
@@ -25,8 +32,8 @@ export const adminStandaloneKeys = {
|
||||
export const adminQueryKeys = {
|
||||
serverConfig: () => ["server-config"] as const,
|
||||
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const,
|
||||
getOrganizations: (filters: AdminGetOrganizationsFilters) =>
|
||||
[adminStandaloneKeys.getOrganizations, { filters }] as const,
|
||||
getOrganizations: (filters?: AdminGetOrganizationsFilters) =>
|
||||
[adminStandaloneKeys.getOrganizations, ...(filters ? [{ filters }] : [])] as const,
|
||||
getIdentities: (filters: AdminGetIdentitiesFilters) =>
|
||||
[adminStandaloneKeys.getIdentities, { filters }] as const,
|
||||
getAdminSlackConfig: () => ["admin-slack-config"] as const,
|
||||
@@ -83,7 +90,18 @@ export const useGetServerConfig = ({
|
||||
enabled: options?.enabled ?? true
|
||||
});
|
||||
|
||||
export const useAdminGetUsers = (filters: AdminGetUsersFilters) => {
|
||||
export const useAdminGetUsers = (
|
||||
filters: AdminGetUsersFilters,
|
||||
options?: Partial<
|
||||
UndefinedInitialDataInfiniteOptions<
|
||||
User[],
|
||||
DefaultError,
|
||||
InfiniteData<User[]>,
|
||||
ReturnType<typeof adminQueryKeys.getUsers>,
|
||||
number
|
||||
>
|
||||
>
|
||||
) => {
|
||||
return useInfiniteQuery({
|
||||
initialPageParam: 0,
|
||||
queryKey: adminQueryKeys.getUsers(filters),
|
||||
@@ -101,7 +119,8 @@ export const useAdminGetUsers = (filters: AdminGetUsersFilters) => {
|
||||
return data.users;
|
||||
},
|
||||
getNextPageParam: (lastPage, pages) =>
|
||||
lastPage.length !== 0 ? pages.length * filters.limit : undefined
|
||||
lastPage.length !== 0 ? pages.length * filters.limit : undefined,
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { OrgMembershipStatus } from "@app/hooks/api/organization/types";
|
||||
|
||||
import { Organization } from "../types";
|
||||
|
||||
export enum LoginMethod {
|
||||
@@ -20,6 +22,7 @@ export type OrganizationWithProjects = Organization & {
|
||||
lastName: string | null;
|
||||
};
|
||||
membershipId: string;
|
||||
status: OrgMembershipStatus;
|
||||
role: string;
|
||||
roleId: string | null;
|
||||
}[];
|
||||
@@ -53,6 +56,7 @@ export type TServerConfig = {
|
||||
fipsEnabled: boolean;
|
||||
envOverrides?: Record<string, string>;
|
||||
paramsFolderSecretDetectionEnabled: boolean;
|
||||
isOfflineUsageReportsEnabled: boolean;
|
||||
};
|
||||
|
||||
export type TUpdateServerConfigDTO = {
|
||||
@@ -142,3 +146,19 @@ export interface TGetEnvOverrides {
|
||||
fields: { key: string; value: string; hasEnvEntry: boolean; description?: string }[];
|
||||
};
|
||||
}
|
||||
|
||||
export type TUsageReportResponse = {
|
||||
filename: string;
|
||||
csvContent: string;
|
||||
signature: string;
|
||||
};
|
||||
|
||||
export type TCreateOrganizationDTO = {
|
||||
name: string;
|
||||
inviteAdminEmails: string[];
|
||||
};
|
||||
|
||||
export type TResendOrgInviteDTO = {
|
||||
organizationId: string;
|
||||
membershipId: string;
|
||||
};
|
||||
|
||||
@@ -20,7 +20,10 @@ export const useCreateAppConnection = () => {
|
||||
|
||||
return data.appConnection;
|
||||
},
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: appConnectionKeys.list() })
|
||||
onSuccess: ({ projectId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.listAvailable(app, projectId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,9 +38,10 @@ export const useUpdateAppConnection = () => {
|
||||
|
||||
return data.appConnection;
|
||||
},
|
||||
onSuccess: (_, { connectionId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list() });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) });
|
||||
onSuccess: ({ projectId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.listAvailable(app, projectId) });
|
||||
// queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -50,9 +54,10 @@ export const useDeleteAppConnection = () => {
|
||||
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { connectionId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list() });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) });
|
||||
onSuccess: ({ projectId, app }) => {
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.list(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: appConnectionKeys.listAvailable(app, projectId) });
|
||||
// queryClient.invalidateQueries({ queryKey: appConnectionKeys.byId(app, connectionId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,29 +5,36 @@ import { apiRequest } from "@app/config/request";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import {
|
||||
TAppConnection,
|
||||
TAppConnectionMap,
|
||||
TAppConnectionOptions,
|
||||
TAvailableAppConnection,
|
||||
TAvailableAppConnectionsResponse,
|
||||
TGetAppConnection,
|
||||
TListAppConnections
|
||||
} from "@app/hooks/api/appConnections/types";
|
||||
import {
|
||||
TAppConnectionOption,
|
||||
TAppConnectionOptionMap
|
||||
} from "@app/hooks/api/appConnections/types/app-options";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
export const appConnectionKeys = {
|
||||
all: ["app-connection"] as const,
|
||||
options: () => [...appConnectionKeys.all, "options"] as const,
|
||||
list: () => [...appConnectionKeys.all, "list"] as const,
|
||||
listAvailable: (app: AppConnection) => [...appConnectionKeys.all, app, "list-available"] as const,
|
||||
listByApp: (app: AppConnection) => [...appConnectionKeys.list(), app],
|
||||
byId: (app: AppConnection, connectionId: string) =>
|
||||
[...appConnectionKeys.all, app, "by-id", connectionId] as const
|
||||
options: (projectType?: ProjectType) =>
|
||||
[...appConnectionKeys.all, "options", ...(projectType ? [projectType] : [])] as const,
|
||||
list: (projectId?: string | null) =>
|
||||
[...appConnectionKeys.all, "list", ...(projectId ? [projectId] : [])] as const,
|
||||
listAvailable: (app: AppConnection, projectId?: string | null) =>
|
||||
[...appConnectionKeys.all, app, "list-available", ...(projectId ? [projectId] : [])] as const
|
||||
// scott: may need these in the future but not using now
|
||||
// getUsage: (app: AppConnection, connectionId: string) =>
|
||||
// [...appConnectionKeys.all, "usage", app, connectionId] as const
|
||||
// listByApp: (app: AppConnection) => [...appConnectionKeys.list(), app],
|
||||
// scott: we will need this once we have individual app connection page
|
||||
// byId: (app: AppConnection, connectionId: string) =>
|
||||
// [...appConnectionKeys.all, app, "by-id", connectionId] as const
|
||||
};
|
||||
|
||||
export const useAppConnectionOptions = (
|
||||
projectType?: ProjectType,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAppConnectionOption[],
|
||||
@@ -39,10 +46,11 @@ export const useAppConnectionOptions = (
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: appConnectionKeys.options(),
|
||||
queryKey: appConnectionKeys.options(projectType),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TAppConnectionOptions>(
|
||||
"/api/v1/app-connections/options"
|
||||
"/api/v1/app-connections/options",
|
||||
{ params: { projectType } }
|
||||
);
|
||||
|
||||
return data.appConnectionOptions;
|
||||
@@ -64,6 +72,7 @@ export const useGetAppConnectionOption = <T extends AppConnection>(app: T) => {
|
||||
};
|
||||
|
||||
export const useListAppConnections = (
|
||||
projectId?: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAppConnection[],
|
||||
@@ -75,10 +84,12 @@ export const useListAppConnections = (
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: appConnectionKeys.list(),
|
||||
queryKey: appConnectionKeys.list(projectId),
|
||||
queryFn: async () => {
|
||||
const { data } =
|
||||
await apiRequest.get<TListAppConnections<TAppConnection>>("/api/v1/app-connections");
|
||||
const { data } = await apiRequest.get<TListAppConnections<TAppConnection>>(
|
||||
"/api/v1/app-connections",
|
||||
{ params: { projectId } }
|
||||
);
|
||||
|
||||
return data.appConnections;
|
||||
},
|
||||
@@ -88,6 +99,7 @@ export const useListAppConnections = (
|
||||
|
||||
export const useListAvailableAppConnections = (
|
||||
app: AppConnection,
|
||||
projectId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAvailableAppConnection[],
|
||||
@@ -99,10 +111,11 @@ export const useListAvailableAppConnections = (
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: appConnectionKeys.listAvailable(app),
|
||||
queryKey: appConnectionKeys.listAvailable(app, projectId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TAvailableAppConnectionsResponse>(
|
||||
`/api/v1/app-connections/${app}/available`
|
||||
`/api/v1/app-connections/${app}/available`,
|
||||
{ params: { projectId } }
|
||||
);
|
||||
|
||||
return data.appConnections;
|
||||
@@ -111,53 +124,82 @@ export const useListAvailableAppConnections = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useListAppConnectionsByApp = <T extends AppConnection>(
|
||||
app: T,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAppConnectionMap[T][],
|
||||
unknown,
|
||||
TAppConnectionMap[T][],
|
||||
ReturnType<typeof appConnectionKeys.listByApp>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: appConnectionKeys.listByApp(app),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TListAppConnections<TAppConnectionMap[T]>>(
|
||||
`/api/v1/app-connections/${app}`
|
||||
);
|
||||
// scott: may need these in the future but not using now
|
||||
// export const useGetAppConnectionUsageById = (
|
||||
// app: AppConnection,
|
||||
// connectionId: string,
|
||||
// options?: Omit<
|
||||
// UseQueryOptions<
|
||||
// AppConnectionUsage,
|
||||
// unknown,
|
||||
// AppConnectionUsage,
|
||||
// ReturnType<typeof appConnectionKeys.getUsage>
|
||||
// >,
|
||||
// "queryKey" | "queryFn"
|
||||
// >
|
||||
// ) => {
|
||||
// return useQuery({
|
||||
// queryKey: appConnectionKeys.getUsage(app, connectionId),
|
||||
// queryFn: async () => {
|
||||
// const { data } = await apiRequest.get<AppConnectionUsage>(
|
||||
// `/api/v1/app-connections/${app}/${connectionId}/usage`
|
||||
// );
|
||||
//
|
||||
// return data;
|
||||
// },
|
||||
// ...options
|
||||
// });
|
||||
// };
|
||||
|
||||
return data.appConnections;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
// scott: may need these in the future but not using now
|
||||
// export const useListAppConnectionsByApp = <T extends AppConnection>(
|
||||
// app: T,
|
||||
// options?: Omit<
|
||||
// UseQueryOptions<
|
||||
// TAppConnectionMap[T][],
|
||||
// unknown,
|
||||
// TAppConnectionMap[T][],
|
||||
// ReturnType<typeof appConnectionKeys.listByApp>
|
||||
// >,
|
||||
// "queryKey" | "queryFn"
|
||||
// >
|
||||
// ) => {
|
||||
// return useQuery({
|
||||
// queryKey: appConnectionKeys.listByApp(app),
|
||||
// queryFn: async () => {
|
||||
// const { data } = await apiRequest.get<TListAppConnections<TAppConnectionMap[T]>>(
|
||||
// `/api/v1/app-connections/${app}`
|
||||
// );
|
||||
//
|
||||
// return data.appConnections;
|
||||
// },
|
||||
// ...options
|
||||
// });
|
||||
// };
|
||||
|
||||
export const useGetAppConnectionById = <T extends AppConnection>(
|
||||
app: T,
|
||||
connectionId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAppConnectionMap[T],
|
||||
unknown,
|
||||
TAppConnectionMap[T],
|
||||
ReturnType<typeof appConnectionKeys.byId>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: appConnectionKeys.byId(app, connectionId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TGetAppConnection<TAppConnectionMap[T]>>(
|
||||
`/api/v1/app-connections/${app}/${connectionId}`
|
||||
);
|
||||
|
||||
return data.appConnection;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
// scott: we will need this once we have individual app connection page
|
||||
// export const useGetAppConnectionById = <T extends AppConnection>(
|
||||
// app: T,
|
||||
// connectionId: string,
|
||||
// options?: Omit<
|
||||
// UseQueryOptions<
|
||||
// TAppConnectionMap[T],
|
||||
// unknown,
|
||||
// TAppConnectionMap[T],
|
||||
// ReturnType<typeof appConnectionKeys.byId>
|
||||
// >,
|
||||
// "queryKey" | "queryFn"
|
||||
// >
|
||||
// ) => {
|
||||
// return useQuery({
|
||||
// queryKey: appConnectionKeys.byId(app, connectionId),
|
||||
// queryFn: async () => {
|
||||
// const { data } = await apiRequest.get<TGetAppConnection<TAppConnectionMap[T]>>(
|
||||
// `/api/v1/app-connections/${app}/${connectionId}`
|
||||
// );
|
||||
//
|
||||
// return data.appConnection;
|
||||
// },
|
||||
// ...options
|
||||
// });
|
||||
// };
|
||||
|
||||
@@ -116,10 +116,11 @@ export type TAppConnection =
|
||||
| TNetlifyConnection
|
||||
| TOktaConnection;
|
||||
|
||||
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
|
||||
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id" | "projectId">;
|
||||
|
||||
export type TListAppConnections<T extends TAppConnection> = { appConnections: T[] };
|
||||
export type TGetAppConnection<T extends TAppConnection> = { appConnection: T };
|
||||
// scott: we will need this once we have individual app connection page
|
||||
// export type TGetAppConnection<T extends TAppConnection> = { appConnection: T };
|
||||
export type TAppConnectionOptions = { appConnectionOptions: TAppConnectionOption[] };
|
||||
export type TAppConnectionResponse = { appConnection: TAppConnection };
|
||||
export type TAvailableAppConnectionsResponse = { appConnections: TAvailableAppConnection[] };
|
||||
@@ -133,6 +134,7 @@ export type TCreateAppConnectionDTO = Pick<
|
||||
| "description"
|
||||
| "isPlatformManagedCredentials"
|
||||
| "gatewayId"
|
||||
| "projectId"
|
||||
>;
|
||||
|
||||
export type TUpdateAppConnectionDTO = Partial<
|
||||
@@ -150,43 +152,60 @@ export type TDeleteAppConnectionDTO = {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export type TAppConnectionMap = {
|
||||
[AppConnection.AWS]: TAwsConnection;
|
||||
[AppConnection.GitHub]: TGitHubConnection;
|
||||
[AppConnection.GitHubRadar]: TGitHubRadarConnection;
|
||||
[AppConnection.GCP]: TGcpConnection;
|
||||
[AppConnection.AzureKeyVault]: TAzureKeyVaultConnection;
|
||||
[AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnection;
|
||||
[AppConnection.AzureClientSecrets]: TAzureClientSecretsConnection;
|
||||
[AppConnection.AzureDevOps]: TAzureDevOpsConnection;
|
||||
[AppConnection.AzureADCS]: TAzureADCSConnection;
|
||||
[AppConnection.Databricks]: TDatabricksConnection;
|
||||
[AppConnection.Humanitec]: THumanitecConnection;
|
||||
[AppConnection.TerraformCloud]: TTerraformCloudConnection;
|
||||
[AppConnection.Vercel]: TVercelConnection;
|
||||
[AppConnection.Postgres]: TPostgresConnection;
|
||||
[AppConnection.MsSql]: TMsSqlConnection;
|
||||
[AppConnection.MySql]: TMySqlConnection;
|
||||
[AppConnection.OracleDB]: TOracleDBConnection;
|
||||
[AppConnection.Camunda]: TCamundaConnection;
|
||||
[AppConnection.Windmill]: TWindmillConnection;
|
||||
[AppConnection.Auth0]: TAuth0Connection;
|
||||
[AppConnection.HCVault]: THCVaultConnection;
|
||||
[AppConnection.LDAP]: TLdapConnection;
|
||||
[AppConnection.TeamCity]: TTeamCityConnection;
|
||||
[AppConnection.OCI]: TOCIConnection;
|
||||
[AppConnection.OnePass]: TOnePassConnection;
|
||||
[AppConnection.Heroku]: THerokuConnection;
|
||||
[AppConnection.Render]: TRenderConnection;
|
||||
[AppConnection.Flyio]: TFlyioConnection;
|
||||
[AppConnection.GitLab]: TGitLabConnection;
|
||||
[AppConnection.Cloudflare]: TCloudflareConnection;
|
||||
[AppConnection.Bitbucket]: TBitbucketConnection;
|
||||
[AppConnection.Zabbix]: TZabbixConnection;
|
||||
[AppConnection.Railway]: TRailwayConnection;
|
||||
[AppConnection.Checkly]: TChecklyConnection;
|
||||
[AppConnection.Supabase]: TSupabaseConnection;
|
||||
[AppConnection.DigitalOcean]: TDigitalOceanConnection;
|
||||
[AppConnection.Netlify]: TNetlifyConnection;
|
||||
[AppConnection.Okta]: TOktaConnection;
|
||||
};
|
||||
// scott: we will need this once we have individual app connection page
|
||||
// export type TAppConnectionMap = {
|
||||
// [AppConnection.AWS]: TAwsConnection;
|
||||
// [AppConnection.GitHub]: TGitHubConnection;
|
||||
// [AppConnection.GitHubRadar]: TGitHubRadarConnection;
|
||||
// [AppConnection.GCP]: TGcpConnection;
|
||||
// [AppConnection.AzureKeyVault]: TAzureKeyVaultConnection;
|
||||
// [AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnection;
|
||||
// [AppConnection.AzureClientSecrets]: TAzureClientSecretsConnection;
|
||||
// [AppConnection.AzureDevOps]: TAzureDevOpsConnection;
|
||||
// [AppConnection.AzureADCS]: TAzureADCSConnection;
|
||||
// [AppConnection.Databricks]: TDatabricksConnection;
|
||||
// [AppConnection.Humanitec]: THumanitecConnection;
|
||||
// [AppConnection.TerraformCloud]: TTerraformCloudConnection;
|
||||
// [AppConnection.Vercel]: TVercelConnection;
|
||||
// [AppConnection.Postgres]: TPostgresConnection;
|
||||
// [AppConnection.MsSql]: TMsSqlConnection;
|
||||
// [AppConnection.MySql]: TMySqlConnection;
|
||||
// [AppConnection.OracleDB]: TOracleDBConnection;
|
||||
// [AppConnection.Camunda]: TCamundaConnection;
|
||||
// [AppConnection.Windmill]: TWindmillConnection;
|
||||
// [AppConnection.Auth0]: TAuth0Connection;
|
||||
// [AppConnection.HCVault]: THCVaultConnection;
|
||||
// [AppConnection.LDAP]: TLdapConnection;
|
||||
// [AppConnection.TeamCity]: TTeamCityConnection;
|
||||
// [AppConnection.OCI]: TOCIConnection;
|
||||
// [AppConnection.OnePass]: TOnePassConnection;
|
||||
// [AppConnection.Heroku]: THerokuConnection;
|
||||
// [AppConnection.Render]: TRenderConnection;
|
||||
// [AppConnection.Flyio]: TFlyioConnection;
|
||||
// [AppConnection.GitLab]: TGitLabConnection;
|
||||
// [AppConnection.Cloudflare]: TCloudflareConnection;
|
||||
// [AppConnection.Bitbucket]: TBitbucketConnection;
|
||||
// [AppConnection.Zabbix]: TZabbixConnection;
|
||||
// [AppConnection.Railway]: TRailwayConnection;
|
||||
// [AppConnection.Checkly]: TChecklyConnection;
|
||||
// [AppConnection.Supabase]: TSupabaseConnection;
|
||||
// [AppConnection.DigitalOcean]: TDigitalOceanConnection;
|
||||
// [AppConnection.Netlify]: TNetlifyConnection;
|
||||
// [AppConnection.Okta]: TOktaConnection;
|
||||
// };
|
||||
|
||||
// scott: we will need this once we have individual app connection page
|
||||
// export type AppConnectionUsage = {
|
||||
// projects: Array<{
|
||||
// id: string;
|
||||
// name: string;
|
||||
// slug: string;
|
||||
// type: ProjectType;
|
||||
// resources: {
|
||||
// secretSyncs: Array<{ id: string; name: string }>;
|
||||
// externalCas: Array<{ id: string; name: string }>;
|
||||
// secretRotations: Array<{ id: string; name: string }>;
|
||||
// dataSources: Array<{ id: string; name: string }>;
|
||||
// };
|
||||
// }>;
|
||||
// };
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
export type TRootAppConnection = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -8,4 +10,11 @@ export type TRootAppConnection = {
|
||||
updatedAt: string;
|
||||
isPlatformManagedCredentials?: boolean;
|
||||
gatewayId?: string | null;
|
||||
projectId?: string | null;
|
||||
project?: {
|
||||
name: string;
|
||||
type: ProjectType;
|
||||
slug: string;
|
||||
id: string;
|
||||
} | null;
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ export const useAssumeProjectPrivileges = () =>
|
||||
useMutation({
|
||||
mutationFn: async ({ projectId, actorId, actorType }: TProjectAssumePrivilegesDTO) => {
|
||||
const { data } = await apiRequest.post<{ message: string }>(
|
||||
`/api/v1/workspace/${projectId}/assume-privileges`,
|
||||
`/api/v1/projects/${projectId}/assume-privileges`,
|
||||
{ actorId, actorType }
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ export const useRemoveAssumeProjectPrivilege = () =>
|
||||
useMutation({
|
||||
mutationFn: async ({ projectId }: { projectId: string }) => {
|
||||
const { data } = await apiRequest.delete<{ message: string }>(
|
||||
`/api/v1/workspace/${projectId}/assume-privileges`
|
||||
`/api/v1/projects/${projectId}/assume-privileges`
|
||||
);
|
||||
|
||||
return data;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum LogProvider {
|
||||
Azure = "azure",
|
||||
Cribl = "cribl",
|
||||
Custom = "custom",
|
||||
Datadog = "datadog",
|
||||
Splunk = "splunk"
|
||||
}
|
||||
@@ -1,6 +1,2 @@
|
||||
export {
|
||||
useCreateAuditLogStream,
|
||||
useDeleteAuditLogStream,
|
||||
useUpdateAuditLogStream
|
||||
} from "./mutations";
|
||||
export { useGetAuditLogStreamDetails, useGetAuditLogStreams } from "./queries";
|
||||
export * from "./mutations";
|
||||
export * from "./queries";
|
||||
|
||||
@@ -12,50 +12,54 @@ import {
|
||||
|
||||
export const useCreateAuditLogStream = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ auditLogStream: TAuditLogStream }, object, TCreateAuditLogStreamDTO>({
|
||||
mutationFn: async (dto) => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ provider, ...params }: TCreateAuditLogStreamDTO) => {
|
||||
const { data } = await apiRequest.post<{ auditLogStream: TAuditLogStream }>(
|
||||
"/api/v1/audit-log-streams",
|
||||
dto
|
||||
`/api/v1/audit-log-streams/${provider}`,
|
||||
params
|
||||
);
|
||||
return data;
|
||||
|
||||
return data.auditLogStream;
|
||||
},
|
||||
onSuccess: (_, { orgId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: auditLogStreamKeys.list(orgId) });
|
||||
}
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: auditLogStreamKeys.list() })
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateAuditLogStream = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ auditLogStream: TAuditLogStream }, object, TUpdateAuditLogStreamDTO>({
|
||||
mutationFn: async (dto) => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ auditLogStreamId, provider, ...params }: TUpdateAuditLogStreamDTO) => {
|
||||
const { data } = await apiRequest.patch<{ auditLogStream: TAuditLogStream }>(
|
||||
`/api/v1/audit-log-streams/${dto.id}`,
|
||||
dto
|
||||
`/api/v1/audit-log-streams/${provider}/${auditLogStreamId}`,
|
||||
params
|
||||
);
|
||||
return data;
|
||||
|
||||
return data.auditLogStream;
|
||||
},
|
||||
onSuccess: (_, { orgId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: auditLogStreamKeys.list(orgId) });
|
||||
onSuccess: (_, { auditLogStreamId, provider }) => {
|
||||
queryClient.invalidateQueries({ queryKey: auditLogStreamKeys.list() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: auditLogStreamKeys.getById(provider, auditLogStreamId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteAuditLogStream = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ auditLogStream: TAuditLogStream }, object, TDeleteAuditLogStreamDTO>({
|
||||
mutationFn: async (dto) => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ auditLogStreamId, provider }: TDeleteAuditLogStreamDTO) => {
|
||||
const { data } = await apiRequest.delete<{ auditLogStream: TAuditLogStream }>(
|
||||
`/api/v1/audit-log-streams/${dto.id}`
|
||||
`/api/v1/audit-log-streams/${provider}/${auditLogStreamId}`
|
||||
);
|
||||
return data;
|
||||
|
||||
return data.auditLogStream;
|
||||
},
|
||||
onSuccess: (_, { orgId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: auditLogStreamKeys.list(orgId) });
|
||||
onSuccess: (_, { auditLogStreamId, provider }) => {
|
||||
queryClient.invalidateQueries({ queryKey: auditLogStreamKeys.list() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: auditLogStreamKeys.getById(provider, auditLogStreamId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,40 +1,89 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TAuditLogStream } from "./types";
|
||||
import { TAuditLogStreamProviderOption } from "./types/provider-options";
|
||||
import { LogProvider } from "./enums";
|
||||
import { TAuditLogStream, TAuditLogStreamProviderMap } from "./types";
|
||||
|
||||
export const auditLogStreamKeys = {
|
||||
list: (orgId: string) => ["audit-log-stream", { orgId }],
|
||||
getById: (id: string) => ["audit-log-stream-details", { id }]
|
||||
all: ["audit-log-stream"] as const,
|
||||
options: () => [...auditLogStreamKeys.all, "options"] as const,
|
||||
list: () => [...auditLogStreamKeys.all, "list"] as const,
|
||||
getById: (provider: string, id: string) =>
|
||||
[...auditLogStreamKeys.all, provider, "get-by-id", id] as const
|
||||
};
|
||||
|
||||
const fetchAuditLogStreams = async () => {
|
||||
const { data } = await apiRequest.get<{ auditLogStreams: TAuditLogStream[] }>(
|
||||
"/api/v1/audit-log-streams"
|
||||
);
|
||||
export const useGetAuditLogStreamOptions = (
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAuditLogStreamProviderOption[],
|
||||
unknown,
|
||||
TAuditLogStreamProviderOption[],
|
||||
ReturnType<typeof auditLogStreamKeys.options>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: auditLogStreamKeys.options(),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ providerOptions: TAuditLogStreamProviderOption[] }>(
|
||||
"/api/v1/audit-log-streams/options"
|
||||
);
|
||||
|
||||
return data.auditLogStreams;
|
||||
};
|
||||
|
||||
export const useGetAuditLogStreams = (orgId: string) =>
|
||||
useQuery({
|
||||
queryKey: auditLogStreamKeys.list(orgId),
|
||||
queryFn: () => fetchAuditLogStreams(),
|
||||
enabled: Boolean(orgId)
|
||||
return data.providerOptions;
|
||||
},
|
||||
...options
|
||||
});
|
||||
|
||||
const fetchAuditLogStreamDetails = async (id: string) => {
|
||||
const { data } = await apiRequest.get<{ auditLogStream: TAuditLogStream }>(
|
||||
`/api/v1/audit-log-streams/${id}`
|
||||
);
|
||||
|
||||
return data.auditLogStream;
|
||||
};
|
||||
|
||||
export const useGetAuditLogStreamDetails = (id: string) =>
|
||||
useQuery({
|
||||
queryKey: auditLogStreamKeys.getById(id),
|
||||
queryFn: () => fetchAuditLogStreamDetails(id),
|
||||
enabled: Boolean(id)
|
||||
export const useListAuditLogStreams = (
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAuditLogStream[],
|
||||
unknown,
|
||||
TAuditLogStream[],
|
||||
ReturnType<typeof auditLogStreamKeys.list>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: auditLogStreamKeys.list(),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ auditLogStreams: TAuditLogStream[] }>(
|
||||
"/api/v1/audit-log-streams"
|
||||
);
|
||||
|
||||
return data.auditLogStreams;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetAuditLogStreamById = <T extends LogProvider>(
|
||||
provider: T,
|
||||
logStreamId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAuditLogStreamProviderMap[T],
|
||||
unknown,
|
||||
TAuditLogStreamProviderMap[T],
|
||||
ReturnType<typeof auditLogStreamKeys.getById>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: auditLogStreamKeys.getById(provider, logStreamId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ auditLogStream: TAuditLogStreamProviderMap[T] }>(
|
||||
`/api/v1/audit-log-streams/${provider}/${logStreamId}`
|
||||
);
|
||||
|
||||
return data.auditLogStream;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
export type LogStreamHeaders = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type TAuditLogStream = {
|
||||
id: string;
|
||||
url: string;
|
||||
headers?: LogStreamHeaders[];
|
||||
};
|
||||
|
||||
export type TCreateAuditLogStreamDTO = {
|
||||
url: string;
|
||||
headers?: LogStreamHeaders[];
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type TUpdateAuditLogStreamDTO = {
|
||||
id: string;
|
||||
url?: string;
|
||||
headers?: LogStreamHeaders[];
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type TDeleteAuditLogStreamDTO = {
|
||||
id: string;
|
||||
orgId: string;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { LogProvider } from "../enums";
|
||||
import { TAzureProviderLogStream } from "./providers/azure-provider";
|
||||
import { TCriblProviderLogStream } from "./providers/cribl-provider";
|
||||
import { TCustomProviderLogStream } from "./providers/custom-provider";
|
||||
import { TDatadogProviderLogStream } from "./providers/datadog-provider";
|
||||
import { TSplunkProviderLogStream } from "./providers/splunk-provider";
|
||||
|
||||
export type TAuditLogStream =
|
||||
| TCustomProviderLogStream
|
||||
| TDatadogProviderLogStream
|
||||
| TSplunkProviderLogStream
|
||||
| TAzureProviderLogStream
|
||||
| TCriblProviderLogStream;
|
||||
|
||||
export type TAuditLogStreamProviderMap = {
|
||||
[LogProvider.Azure]: TAzureProviderLogStream;
|
||||
[LogProvider.Cribl]: TCriblProviderLogStream;
|
||||
[LogProvider.Custom]: TCustomProviderLogStream;
|
||||
[LogProvider.Datadog]: TDatadogProviderLogStream;
|
||||
[LogProvider.Splunk]: TSplunkProviderLogStream;
|
||||
};
|
||||
|
||||
export type TCreateAuditLogStreamDTO = Pick<TAuditLogStream, "provider" | "credentials">;
|
||||
export type TUpdateAuditLogStreamDTO = Pick<TAuditLogStream, "credentials"> & {
|
||||
provider: LogProvider;
|
||||
auditLogStreamId: string;
|
||||
};
|
||||
export type TDeleteAuditLogStreamDTO = {
|
||||
provider: LogProvider;
|
||||
auditLogStreamId: string;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { LogProvider } from "../enums";
|
||||
|
||||
export type TAuditLogStreamProviderOptionBase = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TAuditLogStreamProviderOption = {
|
||||
[P in keyof typeof LogProvider]: TAuditLogStreamProviderOptionBase & {
|
||||
provider: (typeof LogProvider)[P];
|
||||
};
|
||||
}[keyof typeof LogProvider];
|
||||
@@ -0,0 +1,14 @@
|
||||
import { LogProvider } from "../../enums";
|
||||
import { TRootProviderLogStream } from "./root-provider";
|
||||
|
||||
export type TAzureProviderLogStream = TRootProviderLogStream & {
|
||||
provider: LogProvider.Azure;
|
||||
credentials: {
|
||||
tenantId: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
dceUrl: string;
|
||||
dcrId: string;
|
||||
cltName: string;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { LogProvider } from "../../enums";
|
||||
import { TRootProviderLogStream } from "./root-provider";
|
||||
|
||||
export type TCriblProviderLogStream = TRootProviderLogStream & {
|
||||
provider: LogProvider.Cribl;
|
||||
credentials: {
|
||||
url: string;
|
||||
token: string;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { LogProvider } from "../../enums";
|
||||
import { TRootProviderLogStream } from "./root-provider";
|
||||
|
||||
export type TCustomProviderLogStream = TRootProviderLogStream & {
|
||||
provider: LogProvider.Custom;
|
||||
credentials: {
|
||||
url: string;
|
||||
headers: { key: string; value: string }[];
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { LogProvider } from "../../enums";
|
||||
import { TRootProviderLogStream } from "./root-provider";
|
||||
|
||||
export type TDatadogProviderLogStream = TRootProviderLogStream & {
|
||||
provider: LogProvider.Datadog;
|
||||
credentials: {
|
||||
url: string;
|
||||
token: string;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
export type TRootProviderLogStream = {
|
||||
id: string;
|
||||
orgId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { LogProvider } from "../../enums";
|
||||
import { TRootProviderLogStream } from "./root-provider";
|
||||
|
||||
export type TSplunkProviderLogStream = TRootProviderLogStream & {
|
||||
provider: LogProvider.Splunk;
|
||||
credentials: {
|
||||
hostname: string;
|
||||
token: string;
|
||||
};
|
||||
};
|
||||
@@ -20,7 +20,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.CREATE_SECRET]: "Create secret",
|
||||
[EventType.UPDATE_SECRET]: "Update secret",
|
||||
[EventType.DELETE_SECRET]: "Delete secret",
|
||||
[EventType.GET_WORKSPACE_KEY]: "Read project key",
|
||||
[EventType.GET_PROJECT_KEY]: "Read project key",
|
||||
[EventType.AUTHORIZE_INTEGRATION]: "Authorize integration",
|
||||
[EventType.UPDATE_INTEGRATION_AUTH]: "Update integration auth",
|
||||
[EventType.UNAUTHORIZE_INTEGRATION]: "Unauthorize integration",
|
||||
@@ -45,8 +45,8 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.CREATE_ENVIRONMENT]: "Create environment",
|
||||
[EventType.UPDATE_ENVIRONMENT]: "Update environment",
|
||||
[EventType.DELETE_ENVIRONMENT]: "Delete environment",
|
||||
[EventType.ADD_WORKSPACE_MEMBER]: "Add member",
|
||||
[EventType.REMOVE_WORKSPACE_MEMBER]: "Remove member",
|
||||
[EventType.ADD_PROJECT_MEMBER]: "Add member",
|
||||
[EventType.REMOVE_PROJECT_MEMBER]: "Remove member",
|
||||
[EventType.CREATE_FOLDER]: "Create folder",
|
||||
[EventType.UPDATE_FOLDER]: "Update folder",
|
||||
[EventType.DELETE_FOLDER]: "Delete folder",
|
||||
@@ -58,8 +58,8 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.CREATE_SECRET_IMPORT]: "Create secret import",
|
||||
[EventType.UPDATE_SECRET_IMPORT]: "Update secret import",
|
||||
[EventType.DELETE_SECRET_IMPORT]: "Delete secret import",
|
||||
[EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS]: "Update denied permissions",
|
||||
[EventType.UPDATE_USER_WORKSPACE_ROLE]: "Update user role",
|
||||
[EventType.UPDATE_USER_PROJECT_DENIED_PERMISSIONS]: "Update denied permissions",
|
||||
[EventType.UPDATE_USER_PROJECT_ROLE]: "Update user role",
|
||||
[EventType.CREATE_CA]: "Create CA",
|
||||
[EventType.GET_CA]: "Get CA",
|
||||
[EventType.UPDATE_CA]: "Update CA",
|
||||
@@ -132,6 +132,8 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.CREATE_APP_CONNECTION]: "Create App Connection",
|
||||
[EventType.UPDATE_APP_CONNECTION]: "Update App Connection",
|
||||
[EventType.DELETE_APP_CONNECTION]: "Delete App Connection",
|
||||
[EventType.GET_APP_CONNECTION_USAGE]: "Get App Connection Usage",
|
||||
[EventType.MIGRATE_APP_CONNECTION]: "Migrate App Connection",
|
||||
[EventType.GET_SECRET_SYNCS]: "List secret syncs",
|
||||
[EventType.GET_SECRET_SYNC]: "Get Secret Sync",
|
||||
[EventType.CREATE_SECRET_SYNC]: "Create Secret Sync",
|
||||
@@ -192,6 +194,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.UPDATE_IDENTITY_LDAP_AUTH]: "Updated LDAP Auth for identity",
|
||||
[EventType.GET_IDENTITY_LDAP_AUTH]: "Retrieved LDAP Auth for identity",
|
||||
[EventType.REVOKE_IDENTITY_LDAP_AUTH]: "Revoked LDAP Auth for identity",
|
||||
[EventType.CLEAR_IDENTITY_LDAP_AUTH_LOCKOUTS]: "Clear LDAP Auth lockouts",
|
||||
|
||||
[EventType.SECRET_SCANNING_DATA_SOURCE_LIST]: "List Secret Scanning Data Sources",
|
||||
[EventType.SECRET_SCANNING_DATA_SOURCE_CREATE]: "Create Secret Scanning Data Source",
|
||||
|
||||
@@ -26,7 +26,7 @@ export enum EventType {
|
||||
CREATE_SECRET = "create-secret",
|
||||
UPDATE_SECRET = "update-secret",
|
||||
DELETE_SECRET = "delete-secret",
|
||||
GET_WORKSPACE_KEY = "get-workspace-key",
|
||||
GET_PROJECT_KEY = "get-project-key",
|
||||
AUTHORIZE_INTEGRATION = "authorize-integration",
|
||||
UPDATE_INTEGRATION_AUTH = "update-integration-auth",
|
||||
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
|
||||
@@ -54,12 +54,13 @@ export enum EventType {
|
||||
UPDATE_IDENTITY_LDAP_AUTH = "update-identity-ldap-auth",
|
||||
GET_IDENTITY_LDAP_AUTH = "get-identity-ldap-auth",
|
||||
REVOKE_IDENTITY_LDAP_AUTH = "revoke-identity-ldap-auth",
|
||||
CLEAR_IDENTITY_LDAP_AUTH_LOCKOUTS = "clear-identity-ldap-auth-lockouts",
|
||||
|
||||
CREATE_ENVIRONMENT = "create-environment",
|
||||
UPDATE_ENVIRONMENT = "update-environment",
|
||||
DELETE_ENVIRONMENT = "delete-environment",
|
||||
ADD_WORKSPACE_MEMBER = "add-workspace-member",
|
||||
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
|
||||
ADD_PROJECT_MEMBER = "add-project-member",
|
||||
REMOVE_PROJECT_MEMBER = "remove-project-member",
|
||||
CREATE_FOLDER = "create-folder",
|
||||
UPDATE_FOLDER = "update-folder",
|
||||
DELETE_FOLDER = "delete-folder",
|
||||
@@ -71,8 +72,8 @@ export enum EventType {
|
||||
CREATE_SECRET_IMPORT = "create-secret-import",
|
||||
UPDATE_SECRET_IMPORT = "update-secret-import",
|
||||
DELETE_SECRET_IMPORT = "delete-secret-import",
|
||||
UPDATE_USER_WORKSPACE_ROLE = "update-user-workspace-role",
|
||||
UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS = "update-user-workspace-denied-permissions",
|
||||
UPDATE_USER_PROJECT_ROLE = "update-user-project-role",
|
||||
UPDATE_USER_PROJECT_DENIED_PERMISSIONS = "update-user-project-denied-permissions",
|
||||
CREATE_CA = "create-certificate-authority",
|
||||
GET_CA = "get-certificate-authority",
|
||||
UPDATE_CA = "update-certificate-authority",
|
||||
@@ -140,6 +141,8 @@ export enum EventType {
|
||||
CREATE_APP_CONNECTION = "create-app-connection",
|
||||
UPDATE_APP_CONNECTION = "update-app-connection",
|
||||
DELETE_APP_CONNECTION = "delete-app-connection",
|
||||
GET_APP_CONNECTION_USAGE = "get-app-connection-usage",
|
||||
MIGRATE_APP_CONNECTION = "migrate-app-connection",
|
||||
GET_SECRET_SYNCS = "get-secret-syncs",
|
||||
GET_SECRET_SYNC = "get-secret-sync",
|
||||
CREATE_SECRET_SYNC = "create-secret-sync",
|
||||
|
||||
@@ -7,10 +7,10 @@ import { TReactQueryOptions } from "@app/types/reactQuery";
|
||||
import { Actor, AuditLog, TGetAuditLogsFilter } from "./types";
|
||||
|
||||
export const auditLogKeys = {
|
||||
getAuditLogs: (workspaceId: string | null, filters: TGetAuditLogsFilter) =>
|
||||
[{ workspaceId, filters }, "audit-logs"] as const,
|
||||
getAuditLogActorFilterOpts: (workspaceId: string) =>
|
||||
[{ workspaceId }, "audit-log-actor-filters"] as const
|
||||
getAuditLogs: (projectId: string | null, filters: TGetAuditLogsFilter) =>
|
||||
[{ projectId, filters }, "audit-logs"] as const,
|
||||
getAuditLogActorFilterOpts: (projectId: string) =>
|
||||
[{ projectId }, "audit-log-actor-filters"] as const
|
||||
};
|
||||
|
||||
export const useGetAuditLogs = (
|
||||
@@ -56,12 +56,12 @@ export const useGetAuditLogs = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetAuditLogActorFilterOpts = (workspaceId: string) => {
|
||||
export const useGetAuditLogActorFilterOpts = (projectId: string) => {
|
||||
return useQuery({
|
||||
queryKey: auditLogKeys.getAuditLogActorFilterOpts(workspaceId),
|
||||
queryKey: auditLogKeys.getAuditLogActorFilterOpts(projectId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ actors: Actor[] }>(
|
||||
`/api/v1/workspace/${workspaceId}/audit-logs/filters/actors`
|
||||
`/api/v1/projects/${projectId}/audit-logs/filters/actors`
|
||||
);
|
||||
return data.actors;
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ interface DeleteSecretEvent {
|
||||
}
|
||||
|
||||
interface GetWorkspaceKeyEvent {
|
||||
type: EventType.GET_WORKSPACE_KEY;
|
||||
type: EventType.GET_PROJECT_KEY;
|
||||
metadata: {
|
||||
keyId: string;
|
||||
};
|
||||
@@ -361,7 +361,7 @@ interface DeleteEnvironmentEvent {
|
||||
}
|
||||
|
||||
interface AddWorkspaceMemberEvent {
|
||||
type: EventType.ADD_WORKSPACE_MEMBER;
|
||||
type: EventType.ADD_PROJECT_MEMBER;
|
||||
metadata: {
|
||||
userId: string;
|
||||
email: string;
|
||||
@@ -369,7 +369,7 @@ interface AddWorkspaceMemberEvent {
|
||||
}
|
||||
|
||||
interface RemoveWorkspaceMemberEvent {
|
||||
type: EventType.REMOVE_WORKSPACE_MEMBER;
|
||||
type: EventType.REMOVE_PROJECT_MEMBER;
|
||||
metadata: {
|
||||
userId: string;
|
||||
email: string;
|
||||
@@ -500,7 +500,7 @@ interface DeleteSecretImportEvent {
|
||||
}
|
||||
|
||||
interface UpdateUserRole {
|
||||
type: EventType.UPDATE_USER_WORKSPACE_ROLE;
|
||||
type: EventType.UPDATE_USER_PROJECT_ROLE;
|
||||
metadata: {
|
||||
userId: string;
|
||||
email: string;
|
||||
@@ -510,7 +510,7 @@ interface UpdateUserRole {
|
||||
}
|
||||
|
||||
interface UpdateUserDeniedPermissions {
|
||||
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS;
|
||||
type: EventType.UPDATE_USER_PROJECT_DENIED_PERMISSIONS;
|
||||
metadata: {
|
||||
userId: string;
|
||||
email: string;
|
||||
@@ -874,6 +874,13 @@ interface IntegrationSyncedEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface ClearIdentityLdapAuthLockoutsEvent {
|
||||
type: EventType.CLEAR_IDENTITY_LDAP_AUTH_LOCKOUTS;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type Event =
|
||||
| GetSecretsEvent
|
||||
| GetSecretEvent
|
||||
@@ -958,7 +965,8 @@ export type Event =
|
||||
| GetCertificateTemplateEstConfig
|
||||
| UpdateProjectWorkflowIntegrationConfig
|
||||
| GetProjectWorkflowIntegrationConfig
|
||||
| IntegrationSyncedEvent;
|
||||
| IntegrationSyncedEvent
|
||||
| ClearIdentityLdapAuthLockoutsEvent;
|
||||
|
||||
export type AuditLog = {
|
||||
id: string;
|
||||
|
||||
@@ -5,8 +5,8 @@ import { apiRequest } from "@app/config/request";
|
||||
import { SessionStorageKeys } from "@app/const";
|
||||
|
||||
import { organizationKeys } from "../organization/queries";
|
||||
import { projectKeys } from "../projects";
|
||||
import { setAuthToken } from "../reactQuery";
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import {
|
||||
CompleteAccountDTO,
|
||||
CompleteAccountSignupDTO,
|
||||
@@ -101,7 +101,7 @@ export const useSelectOrganization = () => {
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [organizationKeys.getUserOrganizations, workspaceKeys.getAllUserWorkspace]
|
||||
queryKey: [organizationKeys.getUserOrganizations, projectKeys.getAllUserProjects]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import { projectKeys } from "../projects";
|
||||
import { CaType } from "./enums";
|
||||
import { caKeys } from "./queries";
|
||||
import {
|
||||
@@ -118,8 +118,8 @@ export const useImportCaCertificate = (projectId: string) => {
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { caId, projectSlug }) => {
|
||||
queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceCas({ projectSlug }) });
|
||||
onSuccess: (_, { caId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: projectKeys.getProjectCas({ projectId }) });
|
||||
queryClient.invalidateQueries({ queryKey: caKeys.getCaCerts(caId) });
|
||||
queryClient.invalidateQueries({ queryKey: caKeys.getCaCert(caId) });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -142,7 +142,7 @@ export const useCreateCertificate = () => {
|
||||
},
|
||||
onSuccess: (_, { projectSlug }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.forWorkspaceCertificates(projectSlug)
|
||||
queryKey: projectKeys.forProjectCertificates(projectSlug)
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -158,8 +158,8 @@ export const useRenewCa = () => {
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { caId, projectSlug }) => {
|
||||
queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceCas({ projectSlug }) });
|
||||
onSuccess: ({ projectId }, { caId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: projectKeys.getProjectCas({ projectId }) });
|
||||
queryClient.invalidateQueries({ queryKey: caKeys.getCaById(caId) });
|
||||
queryClient.invalidateQueries({ queryKey: caKeys.getCaCert(caId) });
|
||||
queryClient.invalidateQueries({ queryKey: caKeys.getCaCerts(caId) });
|
||||
|
||||
@@ -182,4 +182,5 @@ export type TRenewCaResponse = {
|
||||
certificate: string;
|
||||
certificateChain: string;
|
||||
serialNumber: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { caKeys } from "../ca/queries";
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import { projectKeys } from "../projects";
|
||||
import { certTemplateKeys } from "./queries";
|
||||
import {
|
||||
TCertificateTemplate,
|
||||
@@ -29,7 +29,7 @@ export const useCreateCertTemplate = () => {
|
||||
},
|
||||
onSuccess: ({ caId }, { projectId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceCertificateTemplates(projectId)
|
||||
queryKey: projectKeys.getProjectCertificateTemplates(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: caKeys.getCaCertTemplates(caId) });
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export const useUpdateCertTemplate = () => {
|
||||
},
|
||||
onSuccess: ({ caId }, { projectId, id }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceCertificateTemplates(projectId)
|
||||
queryKey: projectKeys.getProjectCertificateTemplates(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: certTemplateKeys.getCertTemplateById(id) });
|
||||
queryClient.invalidateQueries({ queryKey: caKeys.getCaCertTemplates(caId) });
|
||||
@@ -68,7 +68,7 @@ export const useDeleteCertTemplate = () => {
|
||||
},
|
||||
onSuccess: ({ caId }, { projectId, id }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceCertificateTemplates(projectId)
|
||||
queryKey: projectKeys.getProjectCertificateTemplates(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: certTemplateKeys.getCertTemplateById(id) });
|
||||
queryClient.invalidateQueries({ queryKey: caKeys.getCaCertTemplates(caId) });
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { pkiSubscriberKeys } from "../pkiSubscriber/queries";
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import { projectKeys } from "../projects";
|
||||
import {
|
||||
TCertificate,
|
||||
TDeleteCertDTO,
|
||||
@@ -25,7 +25,7 @@ export const useDeleteCert = () => {
|
||||
},
|
||||
onSuccess: (_, { projectSlug }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.forWorkspaceCertificates(projectSlug)
|
||||
queryKey: projectKeys.forProjectCertificates(projectSlug)
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -47,7 +47,7 @@ export const useRevokeCert = () => {
|
||||
},
|
||||
onSuccess: (_, { projectSlug }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.forWorkspaceCertificates(projectSlug)
|
||||
queryKey: projectKeys.forProjectCertificates(projectSlug)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiSubscriberKeys.allPkiSubscriberCertificates()
|
||||
@@ -68,7 +68,7 @@ export const useImportCertificate = () => {
|
||||
},
|
||||
onSuccess: (_, { projectSlug }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.forWorkspaceCertificates(projectSlug)
|
||||
queryKey: projectKeys.forProjectCertificates(projectSlug)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,27 +7,27 @@ import { Commit, CommitHistoryItem, CommitWithChanges, RollbackPreview } from ".
|
||||
|
||||
export const commitKeys = {
|
||||
count: ({
|
||||
workspaceId,
|
||||
projectId,
|
||||
environment,
|
||||
directory
|
||||
}: {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory?: string;
|
||||
}) => [{ workspaceId, environment, directory }, "folder-commits-count"] as const,
|
||||
}) => [{ projectId, environment, directory }, "folder-commits-count"] as const,
|
||||
|
||||
history: ({
|
||||
workspaceId,
|
||||
projectId,
|
||||
environment,
|
||||
directory
|
||||
}: {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory?: string;
|
||||
}) => [{ workspaceId, environment, directory }, "folder-commits"] as const,
|
||||
}) => [{ projectId, environment, directory }, "folder-commits"] as const,
|
||||
|
||||
details: ({ workspaceId, commitId }: { workspaceId: string; commitId: string }) =>
|
||||
[{ workspaceId, commitId }, "commit-details"] as const,
|
||||
details: ({ projectId, commitId }: { projectId: string; commitId: string }) =>
|
||||
[{ projectId, commitId }, "commit-details"] as const,
|
||||
|
||||
rollbackPreview: ({
|
||||
folderId,
|
||||
@@ -45,11 +45,11 @@ export const commitKeys = {
|
||||
};
|
||||
|
||||
const fetchFolderCommitsCount = async ({
|
||||
workspaceId,
|
||||
projectId,
|
||||
environment,
|
||||
directory
|
||||
}: {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory?: string;
|
||||
}) => {
|
||||
@@ -59,7 +59,7 @@ const fetchFolderCommitsCount = async ({
|
||||
params: {
|
||||
environment,
|
||||
path: directory,
|
||||
projectId: workspaceId
|
||||
projectId
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -67,7 +67,7 @@ const fetchFolderCommitsCount = async ({
|
||||
};
|
||||
|
||||
const fetchFolderCommitHistory = async (
|
||||
workspaceId: string,
|
||||
projectId: string,
|
||||
environment: string,
|
||||
directory: string,
|
||||
offset: number = 0,
|
||||
@@ -87,7 +87,7 @@ const fetchFolderCommitHistory = async (
|
||||
params: {
|
||||
environment,
|
||||
path: directory,
|
||||
projectId: workspaceId,
|
||||
projectId,
|
||||
offset,
|
||||
limit,
|
||||
search,
|
||||
@@ -97,12 +97,12 @@ const fetchFolderCommitHistory = async (
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const fetchCommitDetails = async (workspaceId: string, commitId: string) => {
|
||||
export const fetchCommitDetails = async (projectId: string, commitId: string) => {
|
||||
const { data } = await apiRequest.get<CommitWithChanges>(
|
||||
`/api/v1/pit/commits/${commitId}/changes`,
|
||||
{
|
||||
params: {
|
||||
projectId: workspaceId
|
||||
projectId
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -113,7 +113,7 @@ export const fetchRollbackPreview = async (
|
||||
folderId: string,
|
||||
commitId: string,
|
||||
envSlug: string,
|
||||
workspaceId: string,
|
||||
projectId: string,
|
||||
deepRollback: boolean,
|
||||
secretPath: string
|
||||
): Promise<RollbackPreview[]> => {
|
||||
@@ -125,7 +125,7 @@ export const fetchRollbackPreview = async (
|
||||
environment: envSlug,
|
||||
deepRollback,
|
||||
secretPath,
|
||||
projectId: workspaceId
|
||||
projectId
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -135,7 +135,7 @@ export const fetchRollbackPreview = async (
|
||||
const fetchRollback = async (
|
||||
folderId: string,
|
||||
commitId: string,
|
||||
workspaceId: string,
|
||||
projectId: string,
|
||||
deepRollback: boolean,
|
||||
message?: string,
|
||||
envSlug?: string
|
||||
@@ -147,17 +147,17 @@ const fetchRollback = async (
|
||||
deepRollback,
|
||||
message,
|
||||
environment: envSlug,
|
||||
projectId: workspaceId
|
||||
projectId
|
||||
}
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const fetchRevert = async (commitId: string, workspaceId: string) => {
|
||||
const fetchRevert = async (commitId: string, projectId: string) => {
|
||||
const { data } = await apiRequest.post<{ success: boolean; message: string }>(
|
||||
`/api/v1/pit/commits/${commitId}/revert`,
|
||||
{
|
||||
projectId: workspaceId
|
||||
projectId
|
||||
}
|
||||
);
|
||||
return data;
|
||||
@@ -180,9 +180,9 @@ export const useCommitRevert = ({
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [
|
||||
commitKeys.details({ workspaceId: projectId, commitId }),
|
||||
commitKeys.history({ workspaceId: projectId, environment, directory }),
|
||||
commitKeys.count({ workspaceId: projectId, environment, directory })
|
||||
commitKeys.details({ projectId, commitId }),
|
||||
commitKeys.history({ projectId, environment, directory }),
|
||||
commitKeys.count({ projectId, environment, directory })
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -190,7 +190,7 @@ export const useCommitRevert = ({
|
||||
};
|
||||
|
||||
export const useCommitRollback = ({
|
||||
workspaceId,
|
||||
projectId,
|
||||
commitId,
|
||||
folderId,
|
||||
deepRollback,
|
||||
@@ -198,7 +198,7 @@ export const useCommitRollback = ({
|
||||
directory,
|
||||
envSlug
|
||||
}: {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
commitId: string;
|
||||
folderId: string;
|
||||
deepRollback: boolean;
|
||||
@@ -209,13 +209,13 @@ export const useCommitRollback = ({
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (message: string) =>
|
||||
fetchRollback(folderId, commitId, workspaceId, deepRollback, message, envSlug),
|
||||
fetchRollback(folderId, commitId, projectId, deepRollback, message, envSlug),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [
|
||||
commitKeys.details({ workspaceId, commitId }),
|
||||
commitKeys.history({ workspaceId, environment, directory }),
|
||||
commitKeys.count({ workspaceId, environment, directory })
|
||||
commitKeys.details({ projectId, commitId }),
|
||||
commitKeys.history({ projectId, environment, directory }),
|
||||
commitKeys.count({ projectId, environment, directory })
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -223,31 +223,31 @@ export const useCommitRollback = ({
|
||||
};
|
||||
|
||||
export const useGetFolderCommitsCount = ({
|
||||
workspaceId,
|
||||
projectId,
|
||||
environment,
|
||||
directory,
|
||||
isPaused
|
||||
}: {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory: string;
|
||||
isPaused?: boolean;
|
||||
}) =>
|
||||
useQuery({
|
||||
enabled: Boolean(workspaceId && environment) && !isPaused,
|
||||
queryKey: commitKeys.count({ workspaceId, environment, directory }),
|
||||
queryFn: () => fetchFolderCommitsCount({ workspaceId, environment, directory })
|
||||
enabled: Boolean(projectId && environment) && !isPaused,
|
||||
queryKey: commitKeys.count({ projectId, environment, directory }),
|
||||
queryFn: () => fetchFolderCommitsCount({ projectId, environment, directory })
|
||||
});
|
||||
|
||||
export const useGetFolderCommitHistory = ({
|
||||
workspaceId,
|
||||
projectId,
|
||||
environment,
|
||||
directory,
|
||||
limit = 20,
|
||||
search,
|
||||
sort = "desc"
|
||||
}: {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory: string;
|
||||
limit?: number;
|
||||
@@ -256,10 +256,10 @@ export const useGetFolderCommitHistory = ({
|
||||
}) => {
|
||||
return useInfiniteQuery({
|
||||
initialPageParam: 0,
|
||||
queryKey: [commitKeys.history({ workspaceId, environment, directory }), limit, search, sort],
|
||||
queryKey: [commitKeys.history({ projectId, environment, directory }), limit, search, sort],
|
||||
queryFn: ({ pageParam }) =>
|
||||
fetchFolderCommitHistory(workspaceId, environment, directory, pageParam, limit, search, sort),
|
||||
enabled: Boolean(workspaceId && environment),
|
||||
fetchFolderCommitHistory(projectId, environment, directory, pageParam, limit, search, sort),
|
||||
enabled: Boolean(projectId && environment),
|
||||
select: (data) => {
|
||||
return (data?.pages ?? [])
|
||||
?.map((page) => page.commits)
|
||||
@@ -280,11 +280,11 @@ export const useGetFolderCommitHistory = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetCommitDetails = (workspaceId: string, commitId: string) => {
|
||||
export const useGetCommitDetails = (projectId: string, commitId: string) => {
|
||||
return useQuery({
|
||||
queryKey: commitKeys.details({ workspaceId, commitId }),
|
||||
queryFn: () => fetchCommitDetails(workspaceId, commitId),
|
||||
enabled: Boolean(workspaceId) && Boolean(commitId)
|
||||
queryKey: commitKeys.details({ projectId, commitId }),
|
||||
queryFn: () => fetchCommitDetails(projectId, commitId),
|
||||
enabled: Boolean(projectId) && Boolean(commitId)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./mutations";
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { gatewaysQueryKeys } from "../gateways/queries";
|
||||
|
||||
export const useDeleteGatewayV2ById = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => {
|
||||
return apiRequest.delete(`/api/v2/gateways/${id}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(gatewaysQueryKeys.list());
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export type TGatewayV2 = {
|
||||
id: string;
|
||||
identityId: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
heartbeat: string;
|
||||
identity: {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { queryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TGatewayV2 } from "../gateways-v2/types";
|
||||
import { TGateway } from "./types";
|
||||
|
||||
export const gatewaysQueryKeys = {
|
||||
@@ -11,8 +12,21 @@ export const gatewaysQueryKeys = {
|
||||
queryOptions({
|
||||
queryKey: gatewaysQueryKeys.listKey(),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ gateways: TGateway[] }>("/api/v1/gateways");
|
||||
return data.gateways;
|
||||
const [{ data }, { data: dataV2 }] = await Promise.all([
|
||||
apiRequest.get<{ gateways: TGateway[] }>("/api/v1/gateways"),
|
||||
apiRequest.get<TGatewayV2[]>("/api/v2/gateways")
|
||||
]);
|
||||
|
||||
return [
|
||||
...data.gateways.map((g) => ({
|
||||
...g,
|
||||
isV1: true
|
||||
})),
|
||||
...dataV2.map((g) => ({
|
||||
...g,
|
||||
isV1: false
|
||||
}))
|
||||
];
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
@@ -138,7 +138,7 @@ export const useListProjectGroupUsers = ({
|
||||
});
|
||||
|
||||
const { data } = await apiRequest.get<{ users: TGroupUser[]; totalCount: number }>(
|
||||
`/api/v2/workspace/${projectId}/groups/${id}/users`,
|
||||
`/api/v1/projects/${projectId}/groups/${id}/users`,
|
||||
{
|
||||
params
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
AddIdentityTlsCertAuthDTO,
|
||||
AddIdentityTokenAuthDTO,
|
||||
AddIdentityUniversalAuthDTO,
|
||||
ClearIdentityLdapAuthLockoutsDTO,
|
||||
ClearIdentityUniversalAuthLockoutsDTO,
|
||||
ClientSecretData,
|
||||
CreateIdentityDTO,
|
||||
@@ -1432,7 +1433,11 @@ export const useAddIdentityLdapAuth = () => {
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
accessTokenTrustedIps,
|
||||
lockoutEnabled,
|
||||
lockoutThreshold,
|
||||
lockoutDurationSeconds,
|
||||
lockoutCounterResetSeconds
|
||||
}) => {
|
||||
const { data } = await apiRequest.post<{ identityLdapAuth: IdentityLdapAuth }>(
|
||||
`/api/v1/auth/ldap-auth/identities/${identityId}`,
|
||||
@@ -1448,7 +1453,11 @@ export const useAddIdentityLdapAuth = () => {
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
accessTokenTrustedIps,
|
||||
lockoutEnabled,
|
||||
lockoutThreshold,
|
||||
lockoutDurationSeconds,
|
||||
lockoutCounterResetSeconds
|
||||
}
|
||||
);
|
||||
return data.identityLdapAuth;
|
||||
@@ -1481,7 +1490,11 @@ export const useUpdateIdentityLdapAuth = () => {
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
accessTokenTrustedIps,
|
||||
lockoutEnabled,
|
||||
lockoutThreshold,
|
||||
lockoutDurationSeconds,
|
||||
lockoutCounterResetSeconds
|
||||
}) => {
|
||||
const { data } = await apiRequest.patch<{ identityLdapAuth: IdentityLdapAuth }>(
|
||||
`/api/v1/auth/ldap-auth/identities/${identityId}`,
|
||||
@@ -1497,7 +1510,11 @@ export const useUpdateIdentityLdapAuth = () => {
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
accessTokenTrustedIps,
|
||||
lockoutEnabled,
|
||||
lockoutThreshold,
|
||||
lockoutDurationSeconds,
|
||||
lockoutCounterResetSeconds
|
||||
}
|
||||
);
|
||||
return data.identityLdapAuth;
|
||||
@@ -1532,3 +1549,22 @@ export const useDeleteIdentityLdapAuth = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useClearIdentityLdapAuthLockouts = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<number, object, ClearIdentityLdapAuthLockoutsDTO>({
|
||||
mutationFn: async ({ identityId }) => {
|
||||
const {
|
||||
data: { deleted }
|
||||
} = await apiRequest.post<{ deleted: number }>(
|
||||
`/api/v1/auth/ldap-auth/identities/${identityId}/clear-lockouts`
|
||||
);
|
||||
return deleted;
|
||||
},
|
||||
onSuccess: (_, { identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { OrderByDirection } from "../generic/types";
|
||||
import { OrgIdentityOrderBy } from "../organization/types";
|
||||
import { Project, ProjectUserMembershipTemporaryMode } from "../projects/types";
|
||||
import { TOrgRole } from "../roles/types";
|
||||
import { ProjectUserMembershipTemporaryMode, Workspace } from "../workspace/types";
|
||||
import { IdentityAuthMethod, IdentityJwtConfigurationType } from "./enums";
|
||||
|
||||
export type IdentityTrustedIp = {
|
||||
@@ -54,7 +54,7 @@ export type IdentityMembershipOrg = {
|
||||
export type IdentityMembership = {
|
||||
id: string;
|
||||
identity: Identity;
|
||||
project: Pick<Workspace, "id" | "name" | "type">;
|
||||
project: Pick<Project, "id" | "name" | "type">;
|
||||
roles: Array<
|
||||
{
|
||||
id: string;
|
||||
@@ -603,6 +603,11 @@ export type AddIdentityLdapAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
|
||||
lockoutEnabled: boolean;
|
||||
lockoutThreshold: number;
|
||||
lockoutDurationSeconds: number;
|
||||
lockoutCounterResetSeconds: number;
|
||||
};
|
||||
|
||||
export type UpdateIdentityLdapAuthDTO = {
|
||||
@@ -625,6 +630,11 @@ export type UpdateIdentityLdapAuthDTO = {
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
|
||||
lockoutEnabled?: boolean;
|
||||
lockoutThreshold?: number;
|
||||
lockoutDurationSeconds?: number;
|
||||
lockoutCounterResetSeconds?: number;
|
||||
};
|
||||
|
||||
export type DeleteIdentityLdapAuthDTO = {
|
||||
@@ -650,6 +660,15 @@ export type IdentityLdapAuth = {
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: IdentityTrustedIp[];
|
||||
|
||||
lockoutEnabled: boolean;
|
||||
lockoutThreshold: number;
|
||||
lockoutDurationSeconds: number;
|
||||
lockoutCounterResetSeconds: number;
|
||||
};
|
||||
|
||||
export type ClearIdentityLdapAuthLockoutsDTO = {
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type AddIdentityTokenAuthDTO = {
|
||||
|
||||
@@ -20,7 +20,6 @@ export * from "./identityProjectAdditionalPrivilege";
|
||||
export * from "./incidentContacts";
|
||||
export * from "./integrationAuth";
|
||||
export * from "./integrations";
|
||||
export * from "./keys";
|
||||
export * from "./kms";
|
||||
export * from "./ldapConfig";
|
||||
export * from "./oidcConfig";
|
||||
@@ -29,6 +28,7 @@ export * from "./organization";
|
||||
export * from "./pkiAlerts";
|
||||
export * from "./pkiCollections";
|
||||
export * from "./pkiSubscriber";
|
||||
export * from "./projects";
|
||||
export * from "./projectUserAdditionalPrivilege";
|
||||
export * from "./rateLimit";
|
||||
export * from "./roles";
|
||||
@@ -54,4 +54,3 @@ export * from "./trustedIps";
|
||||
export * from "./users";
|
||||
export * from "./webhooks";
|
||||
export * from "./workflowIntegrations";
|
||||
export * from "./workspace";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { TReactQueryOptions } from "@app/types/reactQuery";
|
||||
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import { projectKeys } from "../projects";
|
||||
import {
|
||||
App,
|
||||
BitbucketEnvironment,
|
||||
@@ -970,7 +970,7 @@ export const useAuthorizeIntegration = () => {
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: { queryKey: workspaceKeys.getWorkspaceAuthorization(res.workspace) }
|
||||
queryKey: { queryKey: projectKeys.getProjectAuthorization(res.workspace) }
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1016,7 +1016,7 @@ export const useSaveIntegrationAccessToken = () => {
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceAuthorization(res.workspace)
|
||||
queryKey: projectKeys.getProjectAuthorization(res.workspace)
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1035,10 +1035,10 @@ export const useDeleteIntegrationAuths = () => {
|
||||
),
|
||||
onSuccess: (_, { workspaceId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceAuthorization(workspaceId)
|
||||
queryKey: projectKeys.getProjectAuthorization(workspaceId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceIntegrations(workspaceId)
|
||||
queryKey: projectKeys.getProjectIntegrations(workspaceId)
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1052,10 +1052,10 @@ export const useDeleteIntegrationAuth = () => {
|
||||
mutationFn: ({ id }) => apiRequest.delete(`/api/v1/integration-auth/${id}`),
|
||||
onSuccess: (_, { workspaceId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceAuthorization(workspaceId)
|
||||
queryKey: projectKeys.getProjectAuthorization(workspaceId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceIntegrations(workspaceId)
|
||||
queryKey: projectKeys.getProjectIntegrations(workspaceId)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "@tanstac
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import { projectKeys } from "../projects";
|
||||
import {
|
||||
IntegrationMetadataSyncMode,
|
||||
TCloudIntegration,
|
||||
@@ -121,7 +121,7 @@ export const useCreateIntegration = () => {
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceIntegrations(res.workspace)
|
||||
queryKey: projectKeys.getProjectIntegrations(res.workspace)
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -141,10 +141,10 @@ export const useDeleteIntegration = () => {
|
||||
),
|
||||
onSuccess: (_, { workspaceId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceIntegrations(workspaceId)
|
||||
queryKey: projectKeys.getProjectIntegrations(workspaceId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getWorkspaceAuthorization(workspaceId)
|
||||
queryKey: projectKeys.getProjectAuthorization(workspaceId)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { useGetUserWsKey, useUploadWsKey } from "./queries";
|
||||
@@ -1,43 +0,0 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { UploadWsKeyDTO, UserWsKeyPair } from "./types";
|
||||
|
||||
const encKeyKeys = {
|
||||
getUserWorkspaceKey: (workspaceID: string) => ["workspace-key-pair", { workspaceID }] as const
|
||||
};
|
||||
|
||||
export const fetchUserWsKey = async (projectId: string) => {
|
||||
const { data } = await apiRequest.get<UserWsKeyPair>(
|
||||
`/api/v2/workspace/${projectId}/encrypted-key`
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useGetUserWsKey = (workspaceID: string) =>
|
||||
useQuery({
|
||||
queryKey: encKeyKeys.getUserWorkspaceKey(workspaceID),
|
||||
queryFn: () => fetchUserWsKey(workspaceID),
|
||||
enabled: Boolean(workspaceID)
|
||||
});
|
||||
|
||||
// mutations
|
||||
export const uploadWsKey = async ({ workspaceId, userId, encryptedKey, nonce }: UploadWsKeyDTO) => {
|
||||
return apiRequest.post(`/api/v1/workspace/${workspaceId}/key`, {
|
||||
key: { userId, encryptedKey, nonce }
|
||||
});
|
||||
};
|
||||
|
||||
export const useUploadWsKey = () =>
|
||||
useMutation<object, object, UploadWsKeyDTO>({
|
||||
mutationFn: async ({ encryptedKey, nonce, userId, workspaceId }) => {
|
||||
return uploadWsKey({
|
||||
workspaceId,
|
||||
userId,
|
||||
encryptedKey,
|
||||
nonce
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
export type UserWsKeyPair = {
|
||||
id: string;
|
||||
encryptedKey: string;
|
||||
nonce: string;
|
||||
sender: Sender;
|
||||
receiver: string;
|
||||
workspace: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
__v: number;
|
||||
};
|
||||
|
||||
export type Sender = {
|
||||
id: string;
|
||||
email: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
__v: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
publicKey: string;
|
||||
};
|
||||
|
||||
export type UploadWsKeyDTO = {
|
||||
userId: string;
|
||||
encryptedKey: string;
|
||||
nonce: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
@@ -75,7 +75,7 @@ export const useUpdateProjectKms = (projectId: string) => {
|
||||
mutationFn: async (
|
||||
updatedData: { type: KmsType.Internal } | { type: KmsType.External; kmsId: string }
|
||||
) => {
|
||||
const { data } = await apiRequest.patch(`/api/v1/workspace/${projectId}/kms`, {
|
||||
const { data } = await apiRequest.patch(`/api/v1/projects/${projectId}/kms`, {
|
||||
kms: updatedData
|
||||
});
|
||||
|
||||
@@ -91,7 +91,7 @@ export const useLoadProjectKmsBackup = (projectId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (backup: string) => {
|
||||
const { data } = await apiRequest.post(`/api/v1/workspace/${projectId}/kms/backup`, {
|
||||
const { data } = await apiRequest.post(`/api/v1/projects/${projectId}/kms/backup`, {
|
||||
backup
|
||||
});
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ export const useGetActiveProjectKms = (projectId: string) => {
|
||||
name: string;
|
||||
isExternal: string;
|
||||
};
|
||||
}>(`/api/v1/workspace/${projectId}/kms`);
|
||||
}>(`/api/v1/projects/${projectId}/kms`);
|
||||
return secretManagerKmsKey;
|
||||
}
|
||||
});
|
||||
@@ -58,7 +58,7 @@ export const useGetActiveProjectKms = (projectId: string) => {
|
||||
export const fetchProjectKmsBackup = async (projectId: string) => {
|
||||
const { data } = await apiRequest.get<{
|
||||
secretManager: string;
|
||||
}>(`/api/v1/workspace/${projectId}/kms/backup`);
|
||||
}>(`/api/v1/projects/${projectId}/kms/backup`);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./mutations";
|
||||
export * from "./queries";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workspaceKeys } from "../workspace";
|
||||
import { projectKeys } from "../projects";
|
||||
|
||||
export const useImportEnvKey = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -34,7 +34,7 @@ export const useImportEnvKey = () => {
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workspaceKeys.getAllUserWorkspace()
|
||||
queryKey: projectKeys.getAllUserProjects()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { ExternalMigrationProviders } from "./types";
|
||||
|
||||
const externalMigrationQueryKeys = {
|
||||
customMigrationAvailable: (provider: ExternalMigrationProviders) => [
|
||||
"custom-migration-available",
|
||||
provider
|
||||
]
|
||||
};
|
||||
|
||||
export const useHasCustomMigrationAvailable = (provider: ExternalMigrationProviders) => {
|
||||
return useQuery({
|
||||
queryKey: externalMigrationQueryKeys.customMigrationAvailable(provider),
|
||||
queryFn: () =>
|
||||
apiRequest.get<{ enabled: boolean }>(
|
||||
`/api/v3/external-migration/custom-migration-enabled/${provider}`
|
||||
)
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum ExternalMigrationProviders {
|
||||
Vault = "vault",
|
||||
EnvKey = "env-key"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user