Merge branch 'heads/main' into ENG-3630

This commit is contained in:
Daniel Hougaard
2025-09-16 17:30:00 +04:00
1050 changed files with 30205 additions and 8323 deletions
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}
@@ -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) => {
@@ -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>
@@ -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";
+1 -1
View File
@@ -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";
+9
View File
@@ -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;
};
+36
View File
@@ -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}`
);
}
}
+12
View File
@@ -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);
};
+5 -5
View File
@@ -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;
+60 -1
View File
@@ -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;
}
});
};
+24 -5
View File
@@ -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
});
};
+20
View File
@@ -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) });
}
});
};
+105 -63
View File
@@ -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",
+8 -5
View File
@@ -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 -7
View File
@@ -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;
}
+14 -6
View File
@@ -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;
+2 -2
View File
@@ -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]
});
}
});
+6 -6
View File
@@ -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) });
+1
View File
@@ -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;
};
};
+16 -2
View File
@@ -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
}))
];
}
})
};
+1 -1
View File
@@ -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)
});
}
});
};
+21 -2
View File
@@ -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 = {
+1 -2
View File
@@ -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
View File
@@ -1 +0,0 @@
export { useGetUserWsKey, useUploadWsKey } from "./queries";
-43
View File
@@ -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
});
}
});
-29
View File
@@ -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;
};
+2 -2
View File
@@ -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
});
+2 -2
View File
@@ -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