mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 19:16:22 +00:00
Merge remote-tracking branch 'origin/main' into feat/kmip-client-management
This commit is contained in:
@@ -284,7 +284,8 @@ export const CmekTable = () => {
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
colorSchema="secondary"
|
||||
className="group/copy duration:0 invisible relative ml-3 group-hover:visible"
|
||||
size="xs"
|
||||
className="group/copy duration:0 invisible relative ml-3 rounded-md group-hover:visible"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(id);
|
||||
setCopyCipherText("Copied");
|
||||
@@ -299,112 +300,116 @@ export const CmekTable = () => {
|
||||
<Badge variant={variant}>{label}</Badge>
|
||||
</Td>
|
||||
<Td>{version}</Td>
|
||||
<Td className="flex justify-end">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton
|
||||
variant="plain"
|
||||
colorSchema="primary"
|
||||
className="ml-4 p-0 data-[state=open]:text-primary-400"
|
||||
ariaLabel="More options"
|
||||
>
|
||||
<FontAwesomeIcon size="lg" icon={faEllipsis} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="min-w-[160px]">
|
||||
<Tooltip
|
||||
content={
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
cannotEncryptData
|
||||
? "Access Restricted"
|
||||
: isDisabled
|
||||
? "Key Disabled"
|
||||
: ""
|
||||
}
|
||||
position="left"
|
||||
>
|
||||
<div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handlePopUpOpen("encryptData", cmek)}
|
||||
icon={<FontAwesomeIcon icon={faLock} />}
|
||||
iconPos="left"
|
||||
isDisabled={cannotEncryptData || isDisabled}
|
||||
>
|
||||
Encrypt Data
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
cannotDecryptData
|
||||
? "Access Restricted"
|
||||
: isDisabled
|
||||
? "Key Disabled"
|
||||
: ""
|
||||
}
|
||||
position="left"
|
||||
>
|
||||
<div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handlePopUpOpen("decryptData", cmek)}
|
||||
icon={<FontAwesomeIcon icon={faLockOpen} />}
|
||||
iconPos="left"
|
||||
isDisabled={cannotDecryptData || isDisabled}
|
||||
>
|
||||
Decrypt Data
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={cannotEditKey ? "Access Restricted" : ""}
|
||||
position="left"
|
||||
>
|
||||
<div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handlePopUpOpen("upsertKey", cmek)}
|
||||
icon={<FontAwesomeIcon icon={faEdit} />}
|
||||
iconPos="left"
|
||||
isDisabled={cannotEditKey}
|
||||
>
|
||||
Edit Key
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={cannotEditKey ? "Access Restricted" : ""}
|
||||
position="left"
|
||||
>
|
||||
<div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDisableCmek(cmek)}
|
||||
icon={
|
||||
<FontAwesomeIcon icon={isDisabled ? faCheckCircle : faCancel} />
|
||||
}
|
||||
iconPos="left"
|
||||
isDisabled={cannotEditKey}
|
||||
>
|
||||
{isDisabled ? "Enable" : "Disable"} Key
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={cannotDeleteKey ? "Access Restricted" : ""}
|
||||
position="left"
|
||||
>
|
||||
<div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handlePopUpOpen("deleteKey", cmek)}
|
||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||
iconPos="left"
|
||||
isDisabled={cannotDeleteKey}
|
||||
>
|
||||
Delete Key
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton
|
||||
variant="plain"
|
||||
colorSchema="primary"
|
||||
className="ml-4 p-0 data-[state=open]:text-primary-400"
|
||||
ariaLabel="More options"
|
||||
>
|
||||
<FontAwesomeIcon size="lg" icon={faEllipsis} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="min-w-[160px]">
|
||||
<Tooltip
|
||||
content={
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
cannotEncryptData
|
||||
? "Access Restricted"
|
||||
: isDisabled
|
||||
? "Key Disabled"
|
||||
: ""
|
||||
}
|
||||
position="left"
|
||||
>
|
||||
<div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handlePopUpOpen("encryptData", cmek)}
|
||||
icon={<FontAwesomeIcon icon={faLock} />}
|
||||
iconPos="left"
|
||||
isDisabled={cannotEncryptData || isDisabled}
|
||||
>
|
||||
Encrypt Data
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
cannotDecryptData
|
||||
? "Access Restricted"
|
||||
: isDisabled
|
||||
? "Key Disabled"
|
||||
: ""
|
||||
}
|
||||
position="left"
|
||||
>
|
||||
<div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handlePopUpOpen("decryptData", cmek)}
|
||||
icon={<FontAwesomeIcon icon={faLockOpen} />}
|
||||
iconPos="left"
|
||||
isDisabled={cannotDecryptData || isDisabled}
|
||||
>
|
||||
Decrypt Data
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={cannotEditKey ? "Access Restricted" : ""}
|
||||
position="left"
|
||||
>
|
||||
<div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handlePopUpOpen("upsertKey", cmek)}
|
||||
icon={<FontAwesomeIcon icon={faEdit} />}
|
||||
iconPos="left"
|
||||
isDisabled={cannotEditKey}
|
||||
>
|
||||
Edit Key
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={cannotEditKey ? "Access Restricted" : ""}
|
||||
position="left"
|
||||
>
|
||||
<div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDisableCmek(cmek)}
|
||||
icon={
|
||||
<FontAwesomeIcon
|
||||
icon={isDisabled ? faCheckCircle : faCancel}
|
||||
/>
|
||||
}
|
||||
iconPos="left"
|
||||
isDisabled={cannotEditKey}
|
||||
>
|
||||
{isDisabled ? "Enable" : "Disable"} Key
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={cannotDeleteKey ? "Access Restricted" : ""}
|
||||
position="left"
|
||||
>
|
||||
<div>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handlePopUpOpen("deleteKey", cmek)}
|
||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||
iconPos="left"
|
||||
isDisabled={cannotDeleteKey}
|
||||
>
|
||||
Delete Key
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, PageHeader } from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionAppConnectionActions,
|
||||
OrgPermissionSubjects
|
||||
} from "@app/context/OrgPermissionContext/types";
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
AddAppConnectionModal,
|
||||
AppConnectionsTable
|
||||
} from "@app/pages/organization/AppConnections/AppConnectionsPage/components";
|
||||
|
||||
export const AppConnectionsPage = withPermission(
|
||||
() => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addConnection"] as const);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800">
|
||||
<Helmet>
|
||||
<title>Infisical | App Connections</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader
|
||||
className="w-full"
|
||||
title={
|
||||
<div className="flex w-full items-center">
|
||||
<span>App Connections</span>
|
||||
<a
|
||||
className="-mt-1.5"
|
||||
href="https://infisical.com/docs/integrations/app-connections/overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="ml-2 inline-block rounded-md bg-yellow/20 px-1.5 text-sm font-normal text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
<span>Docs</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.07rem] ml-1.5 text-[10px]"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionAppConnectionActions.Create}
|
||||
a={OrgPermissionSubjects.AppConnections}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("addConnection");
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
className="ml-auto"
|
||||
>
|
||||
Add Connection
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
}
|
||||
description="Create and configure connections with third-party apps for re-use across Infisical projects"
|
||||
/>
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<AppConnectionsTable />
|
||||
<AddAppConnectionModal
|
||||
isOpen={popUp.addConnection.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addConnection", isOpen)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{
|
||||
action: OrgPermissionAppConnectionActions.Read,
|
||||
subject: OrgPermissionSubjects.AppConnections
|
||||
}
|
||||
);
|
||||
+15
@@ -10,6 +10,9 @@ import { DiscriminativePick } from "@app/types";
|
||||
|
||||
import { AppConnectionHeader } from "../AppConnectionHeader";
|
||||
import { AwsConnectionForm } from "./AwsConnectionForm";
|
||||
import { AzureAppConfigurationConnectionForm } from "./AzureAppConfigurationConnectionForm";
|
||||
import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm";
|
||||
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
|
||||
import { GcpConnectionForm } from "./GcpConnectionForm";
|
||||
import { GitHubConnectionForm } from "./GitHubConnectionForm";
|
||||
|
||||
@@ -53,6 +56,12 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
return <GitHubConnectionForm />;
|
||||
case AppConnection.GCP:
|
||||
return <GcpConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureKeyVault:
|
||||
return <AzureKeyVaultConnectionForm />;
|
||||
case AppConnection.AzureAppConfiguration:
|
||||
return <AzureAppConfigurationConnectionForm />;
|
||||
case AppConnection.Databricks:
|
||||
return <DatabricksConnectionForm onSubmit={onSubmit} />;
|
||||
default:
|
||||
throw new Error(`Unhandled App ${app}`);
|
||||
}
|
||||
@@ -92,6 +101,12 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
return <GitHubConnectionForm appConnection={appConnection} />;
|
||||
case AppConnection.GCP:
|
||||
return <GcpConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureKeyVault:
|
||||
return <AzureKeyVaultConnectionForm appConnection={appConnection} />;
|
||||
case AppConnection.AzureAppConfiguration:
|
||||
return <AzureAppConfigurationConnectionForm appConnection={appConnection} />;
|
||||
case AppConnection.Databricks:
|
||||
return <DatabricksConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
default:
|
||||
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import {
|
||||
AzureAppConfigurationConnectionMethod,
|
||||
TAzureAppConfigurationConnection,
|
||||
useGetAppConnectionOption
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type Props = {
|
||||
appConnection?: TAzureAppConfigurationConnection;
|
||||
};
|
||||
|
||||
const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.AzureAppConfiguration),
|
||||
method: z.nativeEnum(AzureAppConfigurationConnectionMethod),
|
||||
tenantId: z.string().trim().optional()
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const AzureAppConfigurationConnectionForm = ({ appConnection }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
const {
|
||||
option: { oauthClientId },
|
||||
isLoading
|
||||
} = useGetAppConnectionOption(AppConnection.AzureAppConfiguration);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection
|
||||
? {
|
||||
...appConnection,
|
||||
tenantId: appConnection.credentials.tenantId
|
||||
}
|
||||
: {
|
||||
app: AppConnection.AzureAppConfiguration,
|
||||
method: AzureAppConfigurationConnectionMethod.OAuth
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
watch,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
const selectedMethod = watch("method");
|
||||
|
||||
const onSubmit = (formData: FormData) => {
|
||||
setIsRedirecting(true);
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureAppConfigurationConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
);
|
||||
|
||||
switch (formData.method) {
|
||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||
window.location.assign(
|
||||
`https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://azconfig.io/.default%20openid%20offline_access&state=${state}<:>azure-app-configuration`
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
|
||||
}
|
||||
};
|
||||
|
||||
let isMissingConfig: boolean;
|
||||
|
||||
switch (selectedMethod) {
|
||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||
isMissingConfig = !oauthClientId;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${selectedMethod}`);
|
||||
}
|
||||
|
||||
const methodDetails = getAppConnectionMethodDetails(selectedMethod);
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
|
||||
<Controller
|
||||
name="tenantId"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText="The Azure Active Directory (Entra ID) Tenant ID."
|
||||
isError={Boolean(error?.message)}
|
||||
label="Tenant ID"
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="e4f34ea5-ad23-4291-8585-66d20d603cc8" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText={`The method you would like to use to connect with ${
|
||||
APP_CONNECTION_MAP[AppConnection.AzureAppConfiguration].name
|
||||
}. This field cannot be changed after creation.`}
|
||||
errorText={
|
||||
!isLoading && isMissingConfig
|
||||
? `Environment variables have not been configured. ${
|
||||
isInfisicalCloud()
|
||||
? "Please contact Infisical."
|
||||
: `See documentation to configure Azure ${methodDetails.name} Connections.`
|
||||
}`
|
||||
: error?.message
|
||||
}
|
||||
isError={Boolean(error?.message) || isMissingConfig}
|
||||
label="Method"
|
||||
>
|
||||
<Select
|
||||
isDisabled={isUpdate}
|
||||
value={value}
|
||||
onValueChange={(val) => onChange(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{Object.values(AzureAppConfigurationConnectionMethod).map((method) => {
|
||||
return (
|
||||
<SelectItem value={method} key={method}>
|
||||
{getAppConnectionMethodDetails(method).name}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isSubmitting || isRedirecting}
|
||||
isDisabled={isSubmitting || (!isUpdate && !isDirty) || isMissingConfig || isRedirecting}
|
||||
>
|
||||
{isUpdate ? "Reconnect to Azure" : "Connect to Azure"}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import { useGetAppConnectionOption } from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import {
|
||||
AzureKeyVaultConnectionMethod,
|
||||
TAzureKeyVaultConnection
|
||||
} from "@app/hooks/api/appConnections/types/azure-key-vault-connection";
|
||||
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type Props = {
|
||||
appConnection?: TAzureKeyVaultConnection;
|
||||
};
|
||||
|
||||
const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.AzureKeyVault),
|
||||
method: z.nativeEnum(AzureKeyVaultConnectionMethod),
|
||||
tenantId: z.string().trim().optional()
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const AzureKeyVaultConnectionForm = ({ appConnection }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
const {
|
||||
option: { oauthClientId },
|
||||
isLoading
|
||||
} = useGetAppConnectionOption(AppConnection.AzureKeyVault);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection
|
||||
? {
|
||||
...appConnection,
|
||||
tenantId: appConnection.credentials.tenantId
|
||||
}
|
||||
: {
|
||||
app: AppConnection.AzureKeyVault,
|
||||
method: AzureKeyVaultConnectionMethod.OAuth
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
watch,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
const selectedMethod = watch("method");
|
||||
|
||||
const onSubmit = (formData: FormData) => {
|
||||
setIsRedirecting(true);
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureKeyVaultConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
);
|
||||
|
||||
switch (formData.method) {
|
||||
case AzureKeyVaultConnectionMethod.OAuth:
|
||||
window.location.assign(
|
||||
`https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://vault.azure.net/.default%20openid%20offline_access&state=${state}<:>azure-key-vault`
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
|
||||
}
|
||||
};
|
||||
|
||||
let isMissingConfig: boolean;
|
||||
|
||||
switch (selectedMethod) {
|
||||
case AzureKeyVaultConnectionMethod.OAuth:
|
||||
isMissingConfig = !oauthClientId;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${selectedMethod}`);
|
||||
}
|
||||
|
||||
const methodDetails = getAppConnectionMethodDetails(selectedMethod);
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
|
||||
<Controller
|
||||
name="tenantId"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText="The Azure Active Directory (Entra ID) Tenant ID."
|
||||
isError={Boolean(error?.message)}
|
||||
label="Tenant ID"
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="e4f34ea5-ad23-4291-8585-66d20d603cc8" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText={`The method you would like to use to connect with ${
|
||||
APP_CONNECTION_MAP[AppConnection.AzureKeyVault].name
|
||||
}. This field cannot be changed after creation.`}
|
||||
errorText={
|
||||
!isLoading && isMissingConfig
|
||||
? `Environment variables have not been configured. ${
|
||||
isInfisicalCloud()
|
||||
? "Please contact Infisical."
|
||||
: `See documentation to configure Azure ${methodDetails.name} Connections.`
|
||||
}`
|
||||
: error?.message
|
||||
}
|
||||
isError={Boolean(error?.message) || isMissingConfig}
|
||||
label="Method"
|
||||
>
|
||||
<Select
|
||||
isDisabled={isUpdate}
|
||||
value={value}
|
||||
onValueChange={(val) => onChange(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{Object.values(AzureKeyVaultConnectionMethod).map((method) => {
|
||||
return (
|
||||
<SelectItem value={method} key={method}>
|
||||
{getAppConnectionMethodDetails(method).name}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isSubmitting || isRedirecting}
|
||||
isDisabled={isSubmitting || (!isUpdate && !isDirty) || isMissingConfig || isRedirecting}
|
||||
>
|
||||
{isUpdate ? "Reconnect to Azure" : "Connect to Azure"}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
ModalClose,
|
||||
SecretInput,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import {
|
||||
DatabricksConnectionMethod,
|
||||
TDatabricksConnection
|
||||
} from "@app/hooks/api/appConnections/types/databricks-connection";
|
||||
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type Props = {
|
||||
appConnection?: TDatabricksConnection;
|
||||
onSubmit: (formData: FormData) => void;
|
||||
};
|
||||
|
||||
const rootSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.Databricks)
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
rootSchema.extend({
|
||||
method: z.literal(DatabricksConnectionMethod.ServicePrincipal),
|
||||
credentials: z.object({
|
||||
workspaceUrl: z.string().url().trim().min(1, "Workspace URL required"),
|
||||
clientId: z.string().trim().min(1, "Client ID required"),
|
||||
clientSecret: z.string().trim().min(1, "Client secret required")
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const DatabricksConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection ?? {
|
||||
app: AppConnection.Databricks,
|
||||
method: DatabricksConnectionMethod.ServicePrincipal
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText={`The method you would like to use to connect with ${
|
||||
APP_CONNECTION_MAP[AppConnection.Databricks].name
|
||||
}. This field cannot be changed after creation.`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Method"
|
||||
>
|
||||
<Select
|
||||
isDisabled={isUpdate}
|
||||
value={value}
|
||||
onValueChange={(val) => onChange(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{Object.values(DatabricksConnectionMethod).map((method) => {
|
||||
return (
|
||||
<SelectItem value={method} key={method}>
|
||||
{getAppConnectionMethodDetails(method).name}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.workspaceUrl"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Workspace URL"
|
||||
>
|
||||
<Input {...field} placeholder="https://xxx-xxxxxxxx-xxx.cloud.databricks.com" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.clientId"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Client ID"
|
||||
>
|
||||
<Input {...field} placeholder="05810c8f-c44d-4bd0-a327-aab6dd77719b" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.clientSecret"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Client Secret"
|
||||
>
|
||||
<SecretInput
|
||||
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isDirty}
|
||||
>
|
||||
{isUpdate ? "Update Credentials" : "Connect to Databricks"}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -11,9 +11,9 @@ type Props = {
|
||||
};
|
||||
|
||||
export const AppConnectionsSelect = ({ onSelect }: Props) => {
|
||||
const { isLoading, data: appConnectionOptions } = useAppConnectionOptions();
|
||||
const { isPending, data: appConnectionOptions } = useAppConnectionOptions();
|
||||
|
||||
if (isLoading) {
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center py-2.5">
|
||||
<Spinner size="lg" className="text-mineshaft-500" />
|
||||
@@ -23,7 +23,7 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-5 gap-2">
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{appConnectionOptions?.map((option) => (
|
||||
<button
|
||||
type="button"
|
||||
+4
-4
@@ -51,7 +51,7 @@ type AppConnectionFilters = {
|
||||
};
|
||||
|
||||
export const AppConnectionsTable = () => {
|
||||
const { isLoading, data: appConnections = [] } = useListAppConnections();
|
||||
const { isPending, data: appConnections = [] } = useListAppConnections();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"deleteConnection",
|
||||
@@ -159,7 +159,7 @@ export const AppConnectionsTable = () => {
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search integrations..."
|
||||
placeholder="Search connections..."
|
||||
className="flex-1"
|
||||
/>
|
||||
<DropdownMenu>
|
||||
@@ -262,7 +262,7 @@ export const AppConnectionsTable = () => {
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && (
|
||||
{isPending && (
|
||||
<TableSkeleton innerKey="app-connections-table" columns={4} key="app-connections" />
|
||||
)}
|
||||
{filteredAppConnections.slice(offset, perPage * page).map((connection) => (
|
||||
@@ -285,7 +285,7 @@ export const AppConnectionsTable = () => {
|
||||
onChangePerPage={setPerPage}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && !filteredAppConnections?.length && (
|
||||
{!isPending && !filteredAppConnections?.length && (
|
||||
<EmptyState
|
||||
title={
|
||||
appConnections.length
|
||||
+1
-1
@@ -44,7 +44,7 @@ export const DeleteAppConnectionModal = ({ isOpen, onOpenChange, appConnection }
|
||||
isOpen={isOpen}
|
||||
onChange={onOpenChange}
|
||||
title={`Are you sure want to delete ${name}?`}
|
||||
deleteKey="confirm"
|
||||
deleteKey={name}
|
||||
onDeleteApproved={handleDeleteAppConnection}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { AppConnectionsPage } from "./AppConnectionsPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/app-connections/"
|
||||
)({
|
||||
component: AppConnectionsPage,
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||
},
|
||||
{
|
||||
label: "App Connections"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ContentLoader } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import {
|
||||
GitHubConnectionMethod,
|
||||
TGitHubConnection,
|
||||
useCreateAppConnection,
|
||||
useUpdateAppConnection
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
type FormData = Pick<TGitHubConnection, "name" | "method" | "description"> & {
|
||||
returnUrl?: string;
|
||||
connectionId?: string;
|
||||
};
|
||||
|
||||
export const GitHubOAuthCallbackPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const search = useSearch({
|
||||
from: ROUTE_PATHS.Organization.AppConnections.GithubOauthCallbackPage.id
|
||||
});
|
||||
const updateAppConnection = useUpdateAppConnection();
|
||||
const createAppConnection = useCreateAppConnection();
|
||||
|
||||
const { code, state, installation_id: installationId } = search;
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let formData: FormData;
|
||||
|
||||
try {
|
||||
formData = JSON.parse(localStorage.getItem("githubConnectionFormData") ?? "{}") as FormData;
|
||||
} catch {
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Invalid form state, redirecting..."
|
||||
});
|
||||
navigate({ to: "/" });
|
||||
return;
|
||||
}
|
||||
|
||||
// validate state
|
||||
if (state !== localStorage.getItem("latestCSRFToken")) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem("githubConnectionFormData");
|
||||
localStorage.removeItem("latestCSRFToken");
|
||||
|
||||
const { connectionId, name, description, returnUrl } = formData;
|
||||
|
||||
try {
|
||||
if (connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.GitHub,
|
||||
...(installationId
|
||||
? {
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
installationId: installationId as string
|
||||
}
|
||||
}
|
||||
: {
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string
|
||||
}
|
||||
})
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
app: AppConnection.GitHub,
|
||||
name,
|
||||
description,
|
||||
...(installationId
|
||||
? {
|
||||
method: GitHubConnectionMethod.App,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
installationId: installationId as string
|
||||
}
|
||||
}
|
||||
: {
|
||||
method: GitHubConnectionMethod.OAuth,
|
||||
credentials: {
|
||||
code: code as string
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
} catch (e: any) {
|
||||
createNotification({
|
||||
title: `Failed to ${connectionId ? "update" : "add"} GitHub Connection`,
|
||||
text: e.message,
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/settings?selectedTab=app-connections"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${connectionId ? "updated" : "added"} GitHub Connection`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/settings?selectedTab=app-connections"
|
||||
});
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<ContentLoader text="Please wait! Authentication in process." />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ContentLoader } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||
import {
|
||||
AzureAppConfigurationConnectionMethod,
|
||||
AzureKeyVaultConnectionMethod,
|
||||
GitHubConnectionMethod,
|
||||
TAzureAppConfigurationConnection,
|
||||
TAzureKeyVaultConnection,
|
||||
TGitHubConnection,
|
||||
useCreateAppConnection,
|
||||
useUpdateAppConnection
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
type BaseFormData = {
|
||||
returnUrl?: string;
|
||||
connectionId?: string;
|
||||
};
|
||||
|
||||
type GithubFormData = BaseFormData & Pick<TGitHubConnection, "name" | "method" | "description">;
|
||||
|
||||
type AzureKeyVaultFormData = BaseFormData &
|
||||
Pick<TAzureKeyVaultConnection, "name" | "method" | "description"> &
|
||||
Pick<TAzureKeyVaultConnection["credentials"], "tenantId">;
|
||||
|
||||
type AzureAppConfigurationFormData = BaseFormData &
|
||||
Pick<TAzureAppConfigurationConnection, "name" | "method" | "description"> &
|
||||
Pick<TAzureAppConfigurationConnection["credentials"], "tenantId">;
|
||||
|
||||
type FormDataMap = {
|
||||
[AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub };
|
||||
[AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault };
|
||||
[AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & {
|
||||
app: AppConnection.AzureAppConfiguration;
|
||||
};
|
||||
};
|
||||
|
||||
const formDataStorageFieldMap: Partial<Record<AppConnection, string>> = {
|
||||
[AppConnection.GitHub]: "githubConnectionFormData",
|
||||
[AppConnection.AzureKeyVault]: "azureKeyVaultConnectionFormData",
|
||||
[AppConnection.AzureAppConfiguration]: "azureAppConfigurationConnectionFormData"
|
||||
};
|
||||
|
||||
export const OAuthCallbackPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
|
||||
const search = useSearch({
|
||||
from: ROUTE_PATHS.Organization.AppConnections.OauthCallbackPage.id
|
||||
});
|
||||
|
||||
const rawAppConnection = useParams({
|
||||
strict: false,
|
||||
select: (el) => el?.appConnection as AppConnection
|
||||
});
|
||||
|
||||
const updateAppConnection = useUpdateAppConnection();
|
||||
const createAppConnection = useCreateAppConnection();
|
||||
|
||||
const { code, state: rawState, installation_id: installationId } = search;
|
||||
|
||||
const state = rawState.includes("<:>") ? rawState.split("<:>")[0] : rawState;
|
||||
const appConnection = rawState.includes("<:>") ? rawState.split("<:>")[1] : rawAppConnection;
|
||||
|
||||
const clearState = (app: AppConnection) => {
|
||||
if (state !== localStorage.getItem("latestCSRFToken")) {
|
||||
throw new Error("Invalid CSRF token");
|
||||
}
|
||||
|
||||
const dataFieldName = formDataStorageFieldMap[app];
|
||||
|
||||
localStorage.removeItem(dataFieldName!);
|
||||
localStorage.removeItem("latestCSRFToken");
|
||||
};
|
||||
|
||||
const getFormData = <T extends keyof FormDataMap>(app: T): FormDataMap[T] | null => {
|
||||
const dataFieldName = formDataStorageFieldMap[app];
|
||||
|
||||
try {
|
||||
const rawData = JSON.parse(localStorage.getItem(dataFieldName!) ?? "{}");
|
||||
|
||||
return {
|
||||
...rawData,
|
||||
app
|
||||
} as FormDataMap[T];
|
||||
} catch {
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: `Invalid ${app || ""} form state, redirecting...`
|
||||
});
|
||||
navigate({ to: "/" });
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleAzureKeyVault = useCallback(async () => {
|
||||
const formData = getFormData(AppConnection.AzureKeyVault);
|
||||
if (formData === null) return null;
|
||||
|
||||
clearState(AppConnection.AzureKeyVault);
|
||||
|
||||
const { connectionId, name, description, returnUrl } = formData;
|
||||
|
||||
try {
|
||||
if (connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureKeyVault,
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
tenantId: formData.tenantId
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureKeyVault,
|
||||
name,
|
||||
description,
|
||||
method: AzureKeyVaultConnectionMethod.OAuth,
|
||||
credentials: {
|
||||
tenantId: formData.tenantId,
|
||||
code: code as string
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
createNotification({
|
||||
title: `Failed to ${connectionId ? "update" : "add"} Azure Key Vault Connection`,
|
||||
text: err?.message,
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
connectionId,
|
||||
returnUrl,
|
||||
appConnectionName: formData.app
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleAzureAppConfiguration = useCallback(async () => {
|
||||
const formData = getFormData(AppConnection.AzureAppConfiguration);
|
||||
if (formData === null) return null;
|
||||
|
||||
clearState(AppConnection.AzureAppConfiguration);
|
||||
|
||||
const { connectionId, name, description, returnUrl } = formData;
|
||||
|
||||
try {
|
||||
if (connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureAppConfiguration,
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
tenantId: formData.tenantId
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureAppConfiguration,
|
||||
name,
|
||||
description,
|
||||
method: AzureAppConfigurationConnectionMethod.OAuth,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
tenantId: formData.tenantId
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
createNotification({
|
||||
title: `Failed to ${connectionId ? "update" : "add"} Azure App Configuration Connection`,
|
||||
text: err?.message,
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
connectionId,
|
||||
returnUrl,
|
||||
appConnectionName: formData.app
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleGithub = useCallback(async () => {
|
||||
const formData = getFormData(AppConnection.GitHub);
|
||||
if (formData === null) return null;
|
||||
|
||||
clearState(AppConnection.GitHub);
|
||||
|
||||
const { connectionId, name, description, returnUrl } = formData;
|
||||
|
||||
try {
|
||||
if (connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.GitHub,
|
||||
...(installationId
|
||||
? {
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
installationId: installationId as string
|
||||
}
|
||||
}
|
||||
: {
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string
|
||||
}
|
||||
})
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
app: AppConnection.GitHub,
|
||||
name,
|
||||
description,
|
||||
...(installationId
|
||||
? {
|
||||
method: GitHubConnectionMethod.App,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
installationId: installationId as string
|
||||
}
|
||||
}
|
||||
: {
|
||||
method: GitHubConnectionMethod.OAuth,
|
||||
credentials: {
|
||||
code: code as string
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
} catch (e: any) {
|
||||
createNotification({
|
||||
title: `Failed to ${connectionId ? "update" : "add"} GitHub Connection`,
|
||||
text: e.message,
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
connectionId,
|
||||
returnUrl,
|
||||
appConnectionName: formData.app
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Ensure that the localstorage is ready for use, to avoid the form data being malformed
|
||||
useEffect(() => {
|
||||
if (!isReady) {
|
||||
setIsReady(!!localStorage.length);
|
||||
}
|
||||
}, [localStorage.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isReady) return;
|
||||
|
||||
(async () => {
|
||||
let data: { connectionId?: string; returnUrl?: string; appConnectionName?: string } | null =
|
||||
null;
|
||||
|
||||
if (appConnection === AppConnection.GitHub) {
|
||||
data = await handleGithub();
|
||||
} else if (appConnection === AppConnection.AzureKeyVault) {
|
||||
data = await handleAzureKeyVault();
|
||||
} else if (appConnection === AppConnection.AzureAppConfiguration) {
|
||||
data = await handleAzureAppConfiguration();
|
||||
}
|
||||
|
||||
if (data) {
|
||||
createNotification({
|
||||
text: `Successfully ${data.connectionId ? "updated" : "added"} ${data.appConnectionName ? APP_CONNECTION_MAP[data.appConnectionName as AppConnection].name : ""} Connection`,
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
createNotification({
|
||||
text: "Failed to add connection",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
await navigate({
|
||||
to: data?.returnUrl ?? "/organization/app-connections"
|
||||
});
|
||||
})();
|
||||
}, [isReady]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<ContentLoader text="Please wait! Authentication in process." />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -2,7 +2,7 @@ import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { GitHubOAuthCallbackPage } from "./GithubOauthCallbackPage";
|
||||
import { OAuthCallbackPage } from "./OauthCallbackPage";
|
||||
|
||||
const GitHubOAuthCallbackPageQueryParamsSchema = z.object({
|
||||
code: z.coerce.string().catch(""),
|
||||
@@ -11,9 +11,9 @@ const GitHubOAuthCallbackPageQueryParamsSchema = z.object({
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/app-connections/github/oauth/callback"
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback"
|
||||
)({
|
||||
component: GitHubOAuthCallbackPage,
|
||||
component: OAuthCallbackPage,
|
||||
validateSearch: zodValidator(GitHubOAuthCallbackPageQueryParamsSchema),
|
||||
search: {
|
||||
middlewares: [stripSearchParams({ state: "", installation_id: "" })]
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
|
||||
import { AddAppConnectionModal, AppConnectionsTable } from "./components";
|
||||
|
||||
export const AppConnectionsTab = withPermission(
|
||||
() => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addConnection"] as const);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex items-center">
|
||||
<div>
|
||||
<div className="flex items-start gap-1">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">App Connections</p>
|
||||
<a
|
||||
href="https://infisical.com/docs/integrations/app-connections/overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="ml-1 mt-[0.32rem] inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
<span>Docs</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.07rem] ml-1.5 text-[10px]"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<p className="text-sm text-bunker-300">
|
||||
Create and configure connections with third-party apps for re-use across Infisical
|
||||
projects
|
||||
</p>
|
||||
</div>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionAppConnectionActions.Create}
|
||||
a={OrgPermissionSubjects.AppConnections}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("addConnection");
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
className="ml-auto"
|
||||
>
|
||||
Add Connection
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<AppConnectionsTable />
|
||||
<AddAppConnectionModal
|
||||
isOpen={popUp.addConnection.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addConnection", isOpen)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{
|
||||
action: OrgPermissionAppConnectionActions.Read,
|
||||
subject: OrgPermissionSubjects.AppConnections
|
||||
}
|
||||
);
|
||||
@@ -1 +0,0 @@
|
||||
export { AppConnectionsTab } from "./AppConnectionsTab";
|
||||
@@ -4,7 +4,6 @@ import { useSearch } from "@tanstack/react-router";
|
||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
|
||||
import { AppConnectionsTab } from "../AppConnectionsTab";
|
||||
import { AuditLogStreamsTab } from "../AuditLogStreamTab";
|
||||
import { ImportTab } from "../ImportTab";
|
||||
import { KmipTab } from "../KmipTab/OrgKmipTab";
|
||||
@@ -27,7 +26,6 @@ export const OrgTabGroup = () => {
|
||||
key: "workflow-integrations",
|
||||
component: OrgWorkflowIntegrationTab
|
||||
},
|
||||
{ name: "App Connections", key: "app-connections", component: AppConnectionsTab },
|
||||
{ name: "Audit Log Streams", key: "tag-audit-log-streams", component: AuditLogStreamsTab },
|
||||
{ name: "Import", key: "tab-import", component: ImportTab },
|
||||
{ name: "Project Templates", key: "project-templates", component: ProjectTemplatesTab },
|
||||
|
||||
+149
-78
@@ -1,13 +1,19 @@
|
||||
import { useMemo } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FilterableSelect, FormControl, Modal, ModalContent } from "@app/components/v2";
|
||||
import { useOrganization, useWorkspace } from "@app/context";
|
||||
import { CreatableSelect } from "@app/components/v2/CreatableSelect";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useOrgPermission,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import {
|
||||
useAddUsersToOrg,
|
||||
useGetOrgUsers,
|
||||
@@ -19,7 +25,15 @@ import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const addMemberFormSchema = z.object({
|
||||
orgMemberships: z.array(z.object({ label: z.string().trim(), value: z.string().trim() })).min(1),
|
||||
orgMemberships: z
|
||||
.array(
|
||||
z.object({
|
||||
label: z.string().trim(),
|
||||
value: z.string().trim(),
|
||||
isNewInvitee: z.boolean().optional()
|
||||
})
|
||||
)
|
||||
.min(1),
|
||||
projectRoleSlugs: z.array(z.object({ slug: z.string().trim(), name: z.string().trim() })).min(1)
|
||||
});
|
||||
|
||||
@@ -34,6 +48,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { permission } = useOrgPermission();
|
||||
|
||||
const orgId = currentOrg?.id || "";
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
@@ -60,7 +75,12 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
if (!currentWorkspace) return;
|
||||
if (!currentOrg?.id) return;
|
||||
|
||||
const selectedMembers = orgMemberships.map((orgMembership) =>
|
||||
const existingMembers = orgMemberships.filter((membership) => !membership.isNewInvitee);
|
||||
const newInvitees = orgMemberships
|
||||
.filter((membership) => membership.isNewInvitee)
|
||||
.map((membership) => membership.value);
|
||||
|
||||
const selectedMembers = existingMembers.map((orgMembership) =>
|
||||
orgUsers?.find((orgUser) => orgUser.id === orgMembership.value)
|
||||
);
|
||||
|
||||
@@ -76,11 +96,11 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const inviteeEmails = selectedMembers
|
||||
.map((member) => member?.user.username as string)
|
||||
.filter(Boolean);
|
||||
if (inviteeEmails.length) {
|
||||
if (inviteeEmails.length || newInvitees.length) {
|
||||
await addMembersToProject({
|
||||
inviteeEmails,
|
||||
inviteeEmails: [...inviteeEmails, ...newInvitees],
|
||||
organizationId: orgId,
|
||||
organizationRoleSlug: ProjectMembershipRole.Member, // ? This doesn't apply in this case, because we know the users being added are already part of the organization
|
||||
organizationRoleSlug: ProjectMembershipRole.Member, // only applies to new invites
|
||||
projects: [
|
||||
{
|
||||
slug: currentWorkspace.slug,
|
||||
@@ -125,6 +145,13 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const selectedOrgMemberships = watch("orgMemberships");
|
||||
const selectedRoleSlugs = watch("projectRoleSlugs");
|
||||
|
||||
const { append } = useFieldArray<TAddMemberForm>({ control, name: "orgMemberships" });
|
||||
|
||||
const canInviteNewMembers = permission.can(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.Member
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.addMember?.isOpen}
|
||||
@@ -135,19 +162,70 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
title={t("section.members.add-dialog.add-member-to-project") as string}
|
||||
subTitle={t("section.members.add-dialog.user-will-email")}
|
||||
>
|
||||
{filteredOrgUsers.length ? (
|
||||
<form onSubmit={handleSubmit(onAddMembers)}>
|
||||
<div className="flex w-full flex-col items-start gap-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="orgMemberships"
|
||||
render={({ field }) => (
|
||||
<FormControl
|
||||
className="w-full"
|
||||
isError={!!errors.orgMemberships?.length}
|
||||
errorText={errors.orgMemberships?.[0]?.message}
|
||||
label="Invite users to project"
|
||||
>
|
||||
<form onSubmit={handleSubmit(onAddMembers)}>
|
||||
<div className="flex w-full flex-col items-start gap-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="orgMemberships"
|
||||
render={({ field }) => (
|
||||
<FormControl
|
||||
className="w-full"
|
||||
isError={!!errors.orgMemberships?.length}
|
||||
errorText={errors.orgMemberships?.[0]?.message}
|
||||
label="Invite users to project"
|
||||
helperText={
|
||||
canInviteNewMembers
|
||||
? "You can invite new users to your organization by typing out their email address"
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{canInviteNewMembers ? (
|
||||
<CreatableSelect
|
||||
/* eslint-disable-next-line react/no-unstable-nested-components */
|
||||
noOptionsMessage={() => (
|
||||
<>
|
||||
<p>
|
||||
{!filteredOrgUsers.length && (
|
||||
<p>All organization members are already assigned to this project.</p>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
Invite new users to your organization by typing out their email address.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
onCreateOption={(inputValue) =>
|
||||
append({ label: inputValue, value: inputValue, isNewInvitee: true })
|
||||
}
|
||||
formatCreateLabel={(inputValue) => `Invite "${inputValue}"`}
|
||||
isValidNewOption={(input) =>
|
||||
Boolean(input) &&
|
||||
z.string().email().safeParse(input).success &&
|
||||
!orgUsers
|
||||
?.flatMap(({ user }) => {
|
||||
const emails: string[] = [];
|
||||
|
||||
if (user.email) {
|
||||
emails.push(user.email);
|
||||
}
|
||||
|
||||
if (user.username) {
|
||||
emails.push(user.username);
|
||||
}
|
||||
|
||||
return emails;
|
||||
})
|
||||
.includes(input)
|
||||
}
|
||||
className="w-full"
|
||||
placeholder="Add one or more users..."
|
||||
isMulti
|
||||
name="members"
|
||||
options={filteredOrgUsers}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
) : (
|
||||
<FilterableSelect
|
||||
className="w-full"
|
||||
placeholder="Add one or more users..."
|
||||
@@ -157,65 +235,58 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="projectRoleSlugs"
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="w-full"
|
||||
label="Select roles"
|
||||
tooltipText="Select the roles that you wish to assign to the users"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<FilterableSelect
|
||||
options={roles}
|
||||
placeholder="Select roles..."
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isMulti
|
||||
getOptionValue={(option) => option.slug}
|
||||
getOptionLabel={(option) => option.name}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={
|
||||
isSubmitting ||
|
||||
selectedOrgMemberships.length === 0 ||
|
||||
selectedRoleSlugs.length === 0
|
||||
}
|
||||
>
|
||||
Add Members
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("addMember", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>All the users in your organization are already invited.</div>
|
||||
<Link to={"/organization/access-management" as const}>
|
||||
<Button variant="outline_bg">Add users to organization</Button>
|
||||
</Link>
|
||||
<Controller
|
||||
control={control}
|
||||
name="projectRoleSlugs"
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="w-full"
|
||||
label="Select roles"
|
||||
tooltipText="Select the roles that you wish to assign to the users"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<FilterableSelect
|
||||
options={roles}
|
||||
placeholder="Select roles..."
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isMulti
|
||||
getOptionValue={(option) => option.slug}
|
||||
getOptionLabel={(option) => option.name}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={
|
||||
isSubmitting ||
|
||||
selectedOrgMemberships.length === 0 ||
|
||||
selectedRoleSlugs.length === 0
|
||||
}
|
||||
>
|
||||
Add Members
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("addMember", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
+97
-3
@@ -2,12 +2,15 @@ import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faRefresh, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
DeleteActionModal,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@@ -18,8 +21,10 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useWorkspace } from "@app/context";
|
||||
import { usePopUp, useToggle } from "@app/hooks";
|
||||
import { useGetIntegration } from "@app/hooks/api";
|
||||
import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
||||
import { useDeleteIntegration, useSyncIntegration } from "@app/hooks/api/integrations/queries";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { IntegrationAuditLogsSection } from "./components/IntegrationAuditLogsSection";
|
||||
import { IntegrationConnectionSection } from "./components/IntegrationConnectionSection";
|
||||
@@ -38,9 +43,44 @@ export const IntegrationDetailsByIDPage = () => {
|
||||
refetchInterval: 4000
|
||||
});
|
||||
|
||||
const [shouldDeleteSecrets, setShouldDeleteSecrets] = useToggle(false);
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const projectId = currentWorkspace.id;
|
||||
const { mutateAsync: syncIntegration } = useSyncIntegration();
|
||||
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleIntegrationDelete = async (shouldDeleteIntegrationSecrets: boolean) => {
|
||||
try {
|
||||
await deleteIntegration({
|
||||
id: integrationId,
|
||||
workspaceId: currentWorkspace.id,
|
||||
shouldDeleteIntegrationSecrets
|
||||
});
|
||||
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Deleted integration"
|
||||
});
|
||||
|
||||
await navigate({
|
||||
to: `/${ProjectType.SecretManager}/${projectId}/integrations`
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Failed to delete integration"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"deleteConfirmation",
|
||||
"deleteSecretsConfirmation"
|
||||
] as const);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -90,7 +130,10 @@ export const IntegrationDetailsByIDPage = () => {
|
||||
? "hover:!bg-red-500 hover:!text-white"
|
||||
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={() => {}}
|
||||
onClick={() => {
|
||||
setShouldDeleteSecrets.off();
|
||||
handlePopUpOpen("deleteConfirmation", integration);
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -120,6 +163,57 @@ export const IntegrationDetailsByIDPage = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteConfirmation.isOpen}
|
||||
title={`Are you sure want to remove ${integration?.integration || " "} integration for ${
|
||||
integration?.app || "this project"
|
||||
}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
|
||||
deleteKey={
|
||||
(integration?.integration === "azure-app-configuration" &&
|
||||
integration?.app?.split("//")[1]?.split(".")[0]) ||
|
||||
integration?.app ||
|
||||
integration?.owner ||
|
||||
integration?.path ||
|
||||
integration?.integration ||
|
||||
""
|
||||
}
|
||||
onDeleteApproved={async () => {
|
||||
if (shouldDeleteSecrets) {
|
||||
handlePopUpOpen("deleteSecretsConfirmation");
|
||||
return;
|
||||
}
|
||||
|
||||
await handleIntegrationDelete(false);
|
||||
|
||||
handlePopUpClose("deleteConfirmation");
|
||||
}}
|
||||
>
|
||||
{integration?.integration === "github" && (
|
||||
<div className="mt-4">
|
||||
<Checkbox
|
||||
id="delete-integration-secrets"
|
||||
checkIndicatorBg="text-white"
|
||||
onCheckedChange={() => setShouldDeleteSecrets.toggle()}
|
||||
>
|
||||
Delete previously synced secrets from the destination
|
||||
</Checkbox>
|
||||
</div>
|
||||
)}
|
||||
</DeleteActionModal>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteSecretsConfirmation.isOpen}
|
||||
title={`Are you sure you also want to delete secrets on ${integration?.integration}?`}
|
||||
subTitle="By confirming, you acknowledge that all secrets managed by this integration will be removed from the destination. This action is irreversible."
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteSecretsConfirmation", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={async () => {
|
||||
await handleIntegrationDelete(true);
|
||||
handlePopUpClose("deleteSecretsConfirmation");
|
||||
handlePopUpClose("deleteConfirmation");
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { IntegrationDetailsByIDPage } from "./IntegrationsDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Badge, PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import {
|
||||
@@ -29,7 +30,7 @@ export const IntegrationsListPage = () => {
|
||||
const updateSelectedTab = (tab: string) => {
|
||||
navigate({
|
||||
to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path,
|
||||
search: (prev) => ({ ...prev, selectedTab: tab }),
|
||||
search: (prev) => ({ ...prev, selectedTab: tab as IntegrationsListPageTabs }),
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
}
|
||||
@@ -52,7 +53,7 @@ export const IntegrationsListPage = () => {
|
||||
/>
|
||||
<div className="mb-4 mt-4 flex flex-col rounded-r border-l-2 border-l-primary bg-mineshaft-300/5 px-4 py-2.5">
|
||||
<div className="mb-1 flex items-center text-sm">
|
||||
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1 text-primary" />
|
||||
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
|
||||
Integrations Update
|
||||
</div>
|
||||
<p className="mb-2 mt-1 text-sm text-bunker-300">
|
||||
@@ -100,8 +101,7 @@ export const IntegrationsListPage = () => {
|
||||
<TabPanel value={IntegrationsListPageTabs.SecretSyncs}>
|
||||
<ProjectPermissionCan
|
||||
renderGuardBanner
|
||||
passThrough={false}
|
||||
I={ProjectPermissionActions.Read}
|
||||
I={ProjectPermissionSecretSyncActions.Read}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
<SecretSyncsTab />
|
||||
@@ -110,7 +110,6 @@ export const IntegrationsListPage = () => {
|
||||
<TabPanel value={IntegrationsListPageTabs.NativeIntegrations}>
|
||||
<ProjectPermissionCan
|
||||
renderGuardBanner
|
||||
passThrough={false}
|
||||
I={ProjectPermissionActions.Read}
|
||||
a={ProjectPermissionSub.Integrations}
|
||||
>
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
AwsSecretsManagerSyncMappingBehavior,
|
||||
TAwsSecretsManagerSync
|
||||
} from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync";
|
||||
|
||||
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||
import { SecretSyncTableCell, SecretSyncTableCellProps } from "../SecretSyncTableCell";
|
||||
|
||||
type Props = {
|
||||
secretSync: TAwsSecretsManagerSync;
|
||||
};
|
||||
|
||||
export const AwsSecretsManagerSyncDestinationCol = ({ secretSync }: Props) => {
|
||||
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||
|
||||
const { destinationConfig } = secretSync;
|
||||
|
||||
let additionalProps: Pick<
|
||||
SecretSyncTableCellProps,
|
||||
"additionalTooltipContent" | "infoBadge" | "secondaryClassName"
|
||||
> = {};
|
||||
|
||||
if (destinationConfig.mappingBehavior === AwsSecretsManagerSyncMappingBehavior.ManyToOne) {
|
||||
additionalProps = {
|
||||
infoBadge: "secondary",
|
||||
additionalTooltipContent: (
|
||||
<div className="mt-4">
|
||||
<span className="text-xs text-bunker-300">Secret Name:</span>
|
||||
<p className="text-sm">{destinationConfig.secretName}</p>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<SecretSyncTableCell
|
||||
{...additionalProps}
|
||||
secondaryClassName="capitalize"
|
||||
primaryText={primaryText}
|
||||
secondaryText={secondaryText}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { TAzureAppConfigurationSync } from "@app/hooks/api/secretSyncs/types/azure-app-configuration-sync";
|
||||
|
||||
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||
|
||||
type Props = {
|
||||
secretSync: TAzureAppConfigurationSync;
|
||||
};
|
||||
|
||||
export const AzureAppConfigurationDestinationSyncCol = ({ secretSync }: Props) => {
|
||||
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||
|
||||
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { TAzureKeyVaultSync } from "@app/hooks/api/secretSyncs/types/azure-key-vault-sync";
|
||||
|
||||
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||
|
||||
type Props = {
|
||||
secretSync: TAzureKeyVaultSync;
|
||||
};
|
||||
|
||||
export const AzureKeyVaultDestinationSyncCol = ({ secretSync }: Props) => {
|
||||
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||
|
||||
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { TDatabricksSync } from "@app/hooks/api/secretSyncs/types/databricks-sync";
|
||||
|
||||
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||
|
||||
type Props = {
|
||||
secretSync: TDatabricksSync;
|
||||
};
|
||||
|
||||
export const DatabricksSyncDestinationCol = ({ secretSync }: Props) => {
|
||||
const { primaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||
|
||||
return <SecretSyncTableCell primaryText={primaryText} secondaryText="Secret Scope" />;
|
||||
};
|
||||
+12
@@ -1,6 +1,10 @@
|
||||
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
import { AwsParameterStoreSyncDestinationCol } from "./AwsParameterStoreSyncDestinationCol";
|
||||
import { AwsSecretsManagerSyncDestinationCol } from "./AwsSecretsManagerSyncDestinationCol";
|
||||
import { AzureAppConfigurationDestinationSyncCol } from "./AzureAppConfigurationDestinationSyncCol";
|
||||
import { AzureKeyVaultDestinationSyncCol } from "./AzureKeyVaultDestinationSyncCol";
|
||||
import { DatabricksSyncDestinationCol } from "./DatabricksSyncDestinationCol";
|
||||
import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol";
|
||||
import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol";
|
||||
|
||||
@@ -12,10 +16,18 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
|
||||
switch (secretSync.destination) {
|
||||
case SecretSync.AWSParameterStore:
|
||||
return <AwsParameterStoreSyncDestinationCol secretSync={secretSync} />;
|
||||
case SecretSync.AWSSecretsManager:
|
||||
return <AwsSecretsManagerSyncDestinationCol secretSync={secretSync} />;
|
||||
case SecretSync.GitHub:
|
||||
return <GitHubSyncDestinationCol secretSync={secretSync} />;
|
||||
case SecretSync.GCPSecretManager:
|
||||
return <GcpSyncDestinationCol secretSync={secretSync} />;
|
||||
case SecretSync.AzureKeyVault:
|
||||
return <AzureKeyVaultDestinationSyncCol secretSync={secretSync} />;
|
||||
case SecretSync.AzureAppConfiguration:
|
||||
return <AzureAppConfigurationDestinationSyncCol secretSync={secretSync} />;
|
||||
case SecretSync.Databricks:
|
||||
return <DatabricksSyncDestinationCol secretSync={secretSync} />;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
||||
|
||||
+3
-1
@@ -208,7 +208,9 @@ export const SecretSyncRow = ({
|
||||
<FontAwesomeIcon icon={faXmark} className="ml-1 pr-1.5 pt-0.5 text-sm" />
|
||||
<div className="text-xs">Failure Reason</div>
|
||||
</div>
|
||||
<div className="rounded bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
|
||||
<div className="break-words rounded bg-mineshaft-600 p-2 text-xs">
|
||||
{failureMessage}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+5
-1
@@ -30,7 +30,11 @@ export const SecretSyncTableCell = ({
|
||||
content={
|
||||
<>
|
||||
<p className="text-sm">{primaryText}</p>
|
||||
{secondaryText && <p className="text-xs leading-3 text-bunker-300">{secondaryText}</p>}
|
||||
{secondaryText && (
|
||||
<p className={twMerge("text-xs leading-3 text-bunker-300", secondaryClassName)}>
|
||||
{secondaryText}
|
||||
</p>
|
||||
)}
|
||||
{additionalTooltipContent}
|
||||
</>
|
||||
}
|
||||
|
||||
+4
-2
@@ -227,7 +227,8 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
||||
await updateSync.mutateAsync({
|
||||
syncId: secretSync.id,
|
||||
destination: secretSync.destination,
|
||||
isAutoSyncEnabled
|
||||
isAutoSyncEnabled,
|
||||
projectId: secretSync.projectId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
@@ -248,7 +249,8 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
||||
try {
|
||||
await triggerSync.mutateAsync({
|
||||
syncId: secretSync.id,
|
||||
destination: secretSync.destination
|
||||
destination: secretSync.destination,
|
||||
projectId: secretSync.projectId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
|
||||
+16
@@ -17,6 +17,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
||||
primaryText = destinationConfig.path;
|
||||
secondaryText = destinationConfig.region;
|
||||
break;
|
||||
case SecretSync.AWSSecretsManager:
|
||||
primaryText = destinationConfig.region;
|
||||
secondaryText = destinationConfig.mappingBehavior;
|
||||
break;
|
||||
case SecretSync.GitHub:
|
||||
switch (destinationConfig.scope) {
|
||||
case GitHubSyncScope.Organization:
|
||||
@@ -43,6 +47,18 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
||||
primaryText = destinationConfig.projectId;
|
||||
secondaryText = "Global";
|
||||
break;
|
||||
case SecretSync.AzureKeyVault:
|
||||
primaryText = destinationConfig.vaultBaseUrl;
|
||||
break;
|
||||
case SecretSync.AzureAppConfiguration:
|
||||
primaryText = destinationConfig.configurationUrl;
|
||||
if (destinationConfig.label) {
|
||||
secondaryText = `Label - ${destinationConfig.label}`;
|
||||
}
|
||||
break;
|
||||
case SecretSync.Databricks:
|
||||
primaryText = destinationConfig.scope;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { workspaceKeys } from "@app/hooks/api";
|
||||
import { TIntegration } from "@app/hooks/api/integrations/types";
|
||||
import {
|
||||
fetchSecretSyncsByProjectId,
|
||||
secretSyncKeys,
|
||||
TSecretSync
|
||||
} from "@app/hooks/api/secretSyncs";
|
||||
import { fetchWorkspaceIntegrations } from "@app/hooks/api/workspace/queries";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { IntegrationsListPage } from "./IntegrationsListPage";
|
||||
|
||||
const IntegrationsListPageQuerySchema = z.object({
|
||||
selectedTab: z
|
||||
.nativeEnum(IntegrationsListPageTabs)
|
||||
.catch(IntegrationsListPageTabs.NativeIntegrations)
|
||||
selectedTab: z.nativeEnum(IntegrationsListPageTabs).optional()
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -17,7 +23,70 @@ export const Route = createFileRoute(
|
||||
)({
|
||||
component: IntegrationsListPage,
|
||||
validateSearch: zodValidator(IntegrationsListPageQuerySchema),
|
||||
beforeLoad: ({ context }) => {
|
||||
beforeLoad: async ({ context, search, params: { projectId } }) => {
|
||||
if (!search.selectedTab) {
|
||||
let secretSyncs: TSecretSync[];
|
||||
|
||||
try {
|
||||
secretSyncs = await context.queryClient.ensureQueryData({
|
||||
queryKey: secretSyncKeys.list(projectId),
|
||||
queryFn: () => fetchSecretSyncsByProjectId(projectId)
|
||||
});
|
||||
} catch {
|
||||
throw redirect({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId
|
||||
},
|
||||
search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations }
|
||||
});
|
||||
}
|
||||
|
||||
if (secretSyncs.length) {
|
||||
throw redirect({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId
|
||||
},
|
||||
search: { selectedTab: IntegrationsListPageTabs.SecretSyncs }
|
||||
});
|
||||
}
|
||||
|
||||
let integrations: TIntegration[];
|
||||
try {
|
||||
integrations = await context.queryClient.ensureQueryData({
|
||||
queryKey: workspaceKeys.getWorkspaceIntegrations(projectId),
|
||||
queryFn: () => fetchWorkspaceIntegrations(projectId)
|
||||
});
|
||||
} catch {
|
||||
throw redirect({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId
|
||||
},
|
||||
search: { selectedTab: IntegrationsListPageTabs.SecretSyncs }
|
||||
});
|
||||
}
|
||||
|
||||
if (integrations.length) {
|
||||
throw redirect({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId
|
||||
},
|
||||
search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations }
|
||||
});
|
||||
}
|
||||
|
||||
throw redirect({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId
|
||||
},
|
||||
search: { selectedTab: IntegrationsListPageTabs.SecretSyncs }
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
breadcrumbs: [
|
||||
...context.breadcrumbs,
|
||||
|
||||
+4
-2
@@ -92,7 +92,8 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
||||
await updateSync.mutateAsync({
|
||||
syncId: secretSync.id,
|
||||
destination: secretSync.destination,
|
||||
isAutoSyncEnabled
|
||||
isAutoSyncEnabled,
|
||||
projectId: secretSync.projectId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
@@ -111,7 +112,8 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
||||
try {
|
||||
await triggerSyncSecrets.mutateAsync({
|
||||
syncId: secretSync.id,
|
||||
destination: secretSync.destination
|
||||
destination: secretSync.destination,
|
||||
projectId: secretSync.projectId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||
import { Badge } from "@app/components/v2";
|
||||
import { AWS_REGIONS } from "@app/helpers/appConnections";
|
||||
import {
|
||||
AwsSecretsManagerSyncMappingBehavior,
|
||||
TAwsSecretsManagerSync
|
||||
} from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync";
|
||||
|
||||
type Props = {
|
||||
secretSync: TAwsSecretsManagerSync;
|
||||
};
|
||||
|
||||
export const AwsSecretsManagerSyncDestinationSection = ({ secretSync }: Props) => {
|
||||
const { destinationConfig } = secretSync;
|
||||
|
||||
const awsRegion = AWS_REGIONS.find((r) => r.slug === destinationConfig.region);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretSyncLabel label="Region">
|
||||
{awsRegion?.name}
|
||||
<Badge className="ml-1" variant="success">
|
||||
{awsRegion?.slug}{" "}
|
||||
</Badge>
|
||||
</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Mapping Behavior" className="capitalize">
|
||||
{destinationConfig.mappingBehavior}
|
||||
</SecretSyncLabel>
|
||||
{destinationConfig.mappingBehavior === AwsSecretsManagerSyncMappingBehavior.ManyToOne && (
|
||||
<SecretSyncLabel label="Secret Name">{destinationConfig.secretName}</SecretSyncLabel>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||
import { TAzureAppConfigurationSync } from "@app/hooks/api/secretSyncs/types/azure-app-configuration-sync";
|
||||
|
||||
type Props = {
|
||||
secretSync: TAzureAppConfigurationSync;
|
||||
};
|
||||
|
||||
export const AzureAppConfigurationSyncDestinationSection = ({ secretSync }: Props) => {
|
||||
const {
|
||||
destinationConfig: { configurationUrl, label }
|
||||
} = secretSync;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretSyncLabel label="Configuration URL">{configurationUrl}</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Label">{label}</SecretSyncLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||
import { TAzureKeyVaultSync } from "@app/hooks/api/secretSyncs/types/azure-key-vault-sync";
|
||||
|
||||
type Props = {
|
||||
secretSync: TAzureKeyVaultSync;
|
||||
};
|
||||
|
||||
export const AzureKeyVaultSyncDestinationSection = ({ secretSync }: Props) => {
|
||||
const {
|
||||
destinationConfig: { vaultBaseUrl }
|
||||
} = secretSync;
|
||||
|
||||
return <SecretSyncLabel label="Vault URL">{vaultBaseUrl}</SecretSyncLabel>;
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||
import { TDatabricksSync } from "@app/hooks/api/secretSyncs/types/databricks-sync";
|
||||
|
||||
type Props = {
|
||||
secretSync: TDatabricksSync;
|
||||
};
|
||||
|
||||
export const DatabricksSyncDestinationSection = ({ secretSync }: Props) => {
|
||||
const {
|
||||
destinationConfig: { scope }
|
||||
} = secretSync;
|
||||
|
||||
return <SecretSyncLabel label="Secret Scope">{scope}</SecretSyncLabel>;
|
||||
};
|
||||
+18
@@ -10,8 +10,12 @@ import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissi
|
||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { AwsParameterStoreSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection";
|
||||
import { AwsSecretsManagerSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsSecretsManagerSyncDestinationSection";
|
||||
import { DatabricksSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/DatabricksSyncDestinationSection";
|
||||
import { GitHubSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/GitHubSyncDestinationSection";
|
||||
|
||||
import { AzureAppConfigurationSyncDestinationSection } from "./AzureAppConfigurationSyncDestinationSection";
|
||||
import { AzureKeyVaultSyncDestinationSection } from "./AzureKeyVaultSyncDestinationSection";
|
||||
import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection";
|
||||
|
||||
type Props = {
|
||||
@@ -29,12 +33,26 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
|
||||
case SecretSync.AWSParameterStore:
|
||||
DestinationComponents = <AwsParameterStoreSyncDestinationSection secretSync={secretSync} />;
|
||||
break;
|
||||
case SecretSync.AWSSecretsManager:
|
||||
DestinationComponents = <AwsSecretsManagerSyncDestinationSection secretSync={secretSync} />;
|
||||
break;
|
||||
case SecretSync.GitHub:
|
||||
DestinationComponents = <GitHubSyncDestinationSection secretSync={secretSync} />;
|
||||
break;
|
||||
case SecretSync.GCPSecretManager:
|
||||
DestinationComponents = <GcpSyncDestinationSection secretSync={secretSync} />;
|
||||
break;
|
||||
case SecretSync.AzureKeyVault:
|
||||
DestinationComponents = <AzureKeyVaultSyncDestinationSection secretSync={secretSync} />;
|
||||
break;
|
||||
case SecretSync.AzureAppConfiguration:
|
||||
DestinationComponents = (
|
||||
<AzureAppConfigurationSyncDestinationSection secretSync={secretSync} />
|
||||
);
|
||||
break;
|
||||
case SecretSync.Databricks:
|
||||
DestinationComponents = <DatabricksSyncDestinationSection secretSync={secretSync} />;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { SecretSyncDetailsByIDPage } from "./SecretSyncDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.SecretSyncs
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+6
-1
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AWSParameterStoreAuthorizeIntegrationPage } from "./AwsParameterStoreAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -30,6 +30,7 @@ import { useWorkspace } from "@app/context";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth";
|
||||
import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
enum TabSections {
|
||||
Connection = "connection",
|
||||
@@ -158,6 +159,9 @@ export const AWSParameterStoreConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
+6
-1
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AWSParameterStoreConfigurePage } from "./AwsParamterStoreConfigurePage";
|
||||
|
||||
const AwsParameterStoreConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+6
-1
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AWSSecretManagerAuthorizePage } from "./AwsSecretManagerAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -38,6 +38,7 @@ import {
|
||||
IntegrationMappingBehavior,
|
||||
IntegrationMetadataSyncMode
|
||||
} from "@app/hooks/api/integrations/types";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
enum TabSections {
|
||||
Connection = "connection",
|
||||
@@ -206,6 +207,9 @@ export const AwsSecretManagerConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
+6
-1
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AwsSecretManagerConfigurePage } from "./AwsSecretManagerConfigurePage";
|
||||
|
||||
const AwsSecretManagerConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -26,6 +26,7 @@ import { useCreateIntegration } from "@app/hooks/api";
|
||||
import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types";
|
||||
import { useGetWorkspaceById } from "@app/hooks/api/workspace";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
const schema = z.object({
|
||||
baseUrl: z
|
||||
@@ -133,6 +134,9 @@ export const AzureAppConfigurationConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
+6
-1
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AzureAppConfigurationConfigurePage } from "./AzureAppConfigurationConfigurePage";
|
||||
|
||||
const AzureAppConfigurationPageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+6
-1
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AzureAppConfigurationOauthCallbackPage } from "./AzureAppConfigurationOauthCallbackPage";
|
||||
|
||||
export const AzureAppConfigurationOauthCallbackPageQueryParamsSchema = z.object({
|
||||
@@ -22,7 +24,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AzureDevopsAuthorizePage } from "./AzureDevopsAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -21,6 +21,7 @@ import {
|
||||
useGetIntegrationAuthById
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "@app/hooks/api/workspace";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
export const AzureDevopsConfigurePage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -82,6 +83,9 @@ export const AzureDevopsConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AzureDevopsConfigurePage } from "./AzureDevopsConfigurePage";
|
||||
|
||||
const AzureDevopsConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import z from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AzureKeyVaultAuthorizePage } from "./AzureKeyVaultAuthorizePage";
|
||||
|
||||
const PageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -16,6 +16,7 @@ import { useCreateIntegration } from "@app/hooks/api";
|
||||
import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types";
|
||||
import { useGetWorkspaceById } from "@app/hooks/api/workspace";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
const initialSyncBehaviors = [
|
||||
{
|
||||
@@ -90,6 +91,9 @@ export const AzureKeyVaultConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AzureKeyVaultConfigurePage } from "./AzureKeyVaultConfigurePage";
|
||||
|
||||
const AzureKeyVaultConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+6
-1
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { AzureKeyVaultOauthCallbackPage } from "./AzureKeyVaultOauthCallback";
|
||||
|
||||
export const AzureKeyVaultOauthCallbackQueryParamsSchema = z.object({
|
||||
@@ -22,7 +24,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -23,6 +23,7 @@ import {
|
||||
useGetIntegrationAuthBitBucketWorkspaces
|
||||
} from "@app/hooks/api";
|
||||
import { useGetIntegrationAuthBitBucketEnvironments } from "@app/hooks/api/integrationAuth/queries";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
enum BitbucketScope {
|
||||
Repo = "repo",
|
||||
@@ -131,6 +132,9 @@ export const BitbucketConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { BitbucketConfigurePage } from "./BitbucketConfigurePage";
|
||||
|
||||
const BitbucketConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { BitbucketOauthCallbackPage } from "./BitbucketOauthCallbackPage";
|
||||
|
||||
export const BitbucketOauthCallbackQueryParamsSchema = z.object({
|
||||
@@ -22,7 +24,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { ChecklyAuthorizePage } from "./ChecklyAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -27,6 +27,7 @@ import {
|
||||
useGetIntegrationAuthChecklyGroups
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "@app/hooks/api/workspace";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
enum TabSections {
|
||||
Connection = "connection",
|
||||
@@ -115,6 +116,9 @@ export const ChecklyConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { ChecklyConfigurePage } from "./ChecklyConfigurePage";
|
||||
|
||||
const ChecklyConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { CircleCIAuthorizePage } from "./CircleCIAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -21,6 +21,7 @@ import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useCreateIntegration, useGetIntegrationAuthCircleCIOrganizations } from "@app/hooks/api";
|
||||
import { CircleCiScope } from "@app/hooks/api/integrationAuth/types";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
const formSchema = z.discriminatedUnion("scope", [
|
||||
z.object({
|
||||
@@ -105,6 +106,9 @@ export const CircleCIConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { CircleCIConfigurePage } from "./CircleCIConfigurePage";
|
||||
|
||||
const CircleCIConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { Cloud66AuthorizePage } from "./Cloud66AuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -17,6 +17,7 @@ import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
export const Cloud66ConfigurePage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -77,6 +78,9 @@ export const Cloud66ConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { Cloud66ConfigurePage } from "./Cloud66ConfigurePage";
|
||||
|
||||
const Cloud66ConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+6
-1
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { CloudflarePagesAuthorizePage } from "./CloudflarePagesAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -20,6 +20,7 @@ import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
const cloudflareEnvironments = [
|
||||
{ name: "Production", slug: "production" },
|
||||
@@ -96,6 +97,9 @@ export const CloudflarePagesConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
+6
-1
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { CloudflarePagesConfigurePage } from "./CloudflarePagesConfigurePage";
|
||||
|
||||
const CloudflarePagesConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+6
-1
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { CloudflareWorkersAuthorizePage } from "./CloudflareWorkersAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -12,6 +12,7 @@ import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
export const CloudflareWorkersConfigurePage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -69,6 +70,9 @@ export const CloudflareWorkersConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
+6
-1
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { CloudflareWorkersConfigurePage } from "./CloudflareWorkersConfigurePage";
|
||||
|
||||
const CloudflareWorkersConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { CodefreshAuthorizePage } from "./CodefreshAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -17,6 +17,7 @@ import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
export const CodefreshConfigurePage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -73,6 +74,9 @@ export const CodefreshConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { CodefreshConfigurePage } from "./CodefreshConfigurePage";
|
||||
|
||||
const CodefreshConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { DatabricksAuthorizePage } from "./DatabricksAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -26,6 +26,7 @@ import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
export const DatabricksConfigurePage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -89,6 +90,9 @@ export const DatabricksConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { DatabricksConfigurePage } from "./DatabricksConfigurePage";
|
||||
|
||||
const DatabricksConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+6
-1
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { DigitalOceanAppPlatformAuthorizePage } from "./DigitalOceanAppPlatformAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -17,6 +17,7 @@ import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
export const DigitalOceanAppPlatformConfigurePage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -74,6 +75,9 @@ export const DigitalOceanAppPlatformConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
+6
-1
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { DigitalOceanAppPlatformConfigurePage } from "./DigitalOceanAppPlatformConfigurePage";
|
||||
|
||||
const DigitalOceanAppPlatformConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { FlyioAuthorizePage } from "./FlyioAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -27,6 +27,7 @@ import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
export const FlyioConfigurePage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -88,6 +89,9 @@ export const FlyioConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { FlyioConfigurePage } from "./FlyioConfigurePage";
|
||||
|
||||
const FlyioConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+6
-1
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { GcpSecretManagerAuthorizePage } from "./GcpSecretManagerAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -33,6 +33,7 @@ import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
enum TabSections {
|
||||
Connection = "connection",
|
||||
@@ -153,6 +154,9 @@ export const GcpSecretManagerConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
+6
-1
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { GcpSecretManagerConfigurePage } from "./GcpSecretManagerConfigurePage";
|
||||
|
||||
const GcpConfigurePageQueryParamsSchema = z.object({
|
||||
@@ -21,7 +23,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+6
-1
@@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { GcpSecretManagerOauthCallbackPage } from "./GcpSecretManagerOauthCallbackPage";
|
||||
|
||||
export const GcpSecretManagerOAuthCallbackPageQueryParamsSchema = z.object({
|
||||
@@ -22,7 +24,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { GithubAuthorizePage } from "./GithubAuthorizePage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
@@ -14,7 +16,10 @@ export const Route = createFileRoute(
|
||||
label: "Integrations",
|
||||
link: linkOptions({
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params
|
||||
params,
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
+4
@@ -45,6 +45,7 @@ import {
|
||||
useGetIntegrationAuthGithubEnvs,
|
||||
useGetIntegrationAuthGithubOrgs
|
||||
} from "@app/hooks/api";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
enum TabSections {
|
||||
Connection = "connection",
|
||||
@@ -269,6 +270,9 @@ export const GithubConfigurePage = () => {
|
||||
to: "/secret-manager/$projectId/integrations",
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user