mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-21 00:17:46 +00:00
feature: add suport for project scoped app connections
This commit is contained in:
+13
-56
@@ -1,24 +1,17 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faInfoCircle } 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 { 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";
|
||||
import { 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>
|
||||
@@ -30,54 +23,18 @@ export const AppConnectionsPage = withPermission(
|
||||
<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"
|
||||
title="App Connections"
|
||||
description="Manage organization App Connections"
|
||||
/>
|
||||
<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 className="mb-4 flex w-full flex-col rounded-md border border-blue-500/50 bg-blue-500/30 px-4 py-2 text-sm text-blue-200">
|
||||
<div className="flex items-center">
|
||||
<FontAwesomeIcon icon={faInfoCircle} className="mb-0.5 mr-2 text-sm" />
|
||||
<span className="text-sm text-blue-200">
|
||||
App connections can also be created and managed independently in projects now.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<AppConnectionsTable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+30
-6
@@ -3,6 +3,7 @@ import { useState } from "react";
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { TAppConnection } from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { AppConnectionForm } from "./AppConnectionForm";
|
||||
import { AppConnectionsSelect } from "./AppConnectionList";
|
||||
@@ -10,29 +11,44 @@ import { AppConnectionsSelect } from "./AppConnectionList";
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
projectId?: string;
|
||||
projectType?: ProjectType;
|
||||
app?: AppConnection;
|
||||
onComplete?: (appConnection: TAppConnection) => void;
|
||||
};
|
||||
|
||||
type ContentProps = {
|
||||
onComplete: (appConnection: TAppConnection) => void;
|
||||
projectId?: string;
|
||||
projectType?: ProjectType;
|
||||
app?: AppConnection;
|
||||
};
|
||||
|
||||
const Content = ({ onComplete }: ContentProps) => {
|
||||
const Content = ({ onComplete, projectId, projectType, app }: ContentProps) => {
|
||||
const [selectedApp, setSelectedApp] = useState<AppConnection | null>(null);
|
||||
|
||||
if (selectedApp) {
|
||||
if (app ?? selectedApp) {
|
||||
return (
|
||||
<AppConnectionForm
|
||||
onComplete={onComplete}
|
||||
onBack={() => setSelectedApp(null)}
|
||||
app={selectedApp}
|
||||
app={(app ?? selectedApp)!}
|
||||
projectId={projectId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <AppConnectionsSelect onSelect={setSelectedApp} />;
|
||||
return <AppConnectionsSelect onSelect={setSelectedApp} projectType={projectType} />;
|
||||
};
|
||||
|
||||
export const AddAppConnectionModal = ({ isOpen, onOpenChange }: Props) => {
|
||||
export const AddAppConnectionModal = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
projectId,
|
||||
projectType,
|
||||
app,
|
||||
onComplete
|
||||
}: Props) => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent
|
||||
@@ -40,7 +56,15 @@ export const AddAppConnectionModal = ({ isOpen, onOpenChange }: Props) => {
|
||||
title="Add Connection"
|
||||
subTitle="Select a third-party app to connect to."
|
||||
>
|
||||
<Content onComplete={() => onOpenChange(false)} />
|
||||
<Content
|
||||
projectId={projectId}
|
||||
projectType={projectType}
|
||||
app={app}
|
||||
onComplete={(appConnection) => {
|
||||
if (onComplete) onComplete(appConnection);
|
||||
onOpenChange(false);
|
||||
}}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
+61
-20
@@ -52,12 +52,15 @@ type FormProps = {
|
||||
onComplete: (appConnection: TAppConnection) => void;
|
||||
} & ({ appConnection: TAppConnection } | { app: AppConnection });
|
||||
|
||||
type CreateFormProps = FormProps & { app: AppConnection };
|
||||
type CreateFormProps = FormProps & {
|
||||
app: AppConnection;
|
||||
projectId?: string;
|
||||
};
|
||||
type UpdateFormProps = FormProps & {
|
||||
appConnection: TAppConnection;
|
||||
};
|
||||
|
||||
const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => {
|
||||
const createAppConnection = useCreateAppConnection();
|
||||
const { name: appName } = APP_CONNECTION_MAP[app];
|
||||
|
||||
@@ -68,7 +71,10 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
>
|
||||
) => {
|
||||
try {
|
||||
const connection = await createAppConnection.mutateAsync(formData);
|
||||
const connection = await createAppConnection.mutateAsync({
|
||||
...formData,
|
||||
projectId
|
||||
});
|
||||
createNotification({
|
||||
text: `Successfully added ${appName} Connection`,
|
||||
type: "success"
|
||||
@@ -88,15 +94,15 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
case AppConnection.AWS:
|
||||
return <AwsConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.GitHub:
|
||||
return <GitHubConnectionForm />;
|
||||
return <GitHubConnectionForm projectId={projectId} />;
|
||||
case AppConnection.GitHubRadar:
|
||||
return <GitHubRadarConnectionForm />;
|
||||
return <GitHubRadarConnectionForm projectId={projectId} />;
|
||||
case AppConnection.GCP:
|
||||
return <GcpConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureKeyVault:
|
||||
return <AzureKeyVaultConnectionForm onSubmit={onSubmit} />;
|
||||
return <AzureKeyVaultConnectionForm onSubmit={onSubmit} projectId={projectId} />;
|
||||
case AppConnection.AzureAppConfiguration:
|
||||
return <AzureAppConfigurationConnectionForm onSubmit={onSubmit} />;
|
||||
return <AzureAppConfigurationConnectionForm onSubmit={onSubmit} projectId={projectId} />;
|
||||
case AppConnection.AzureADCS:
|
||||
return <AzureADCSConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Databricks:
|
||||
@@ -118,9 +124,9 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
case AppConnection.Camunda:
|
||||
return <CamundaConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureClientSecrets:
|
||||
return <AzureClientSecretsConnectionForm onSubmit={onSubmit} />;
|
||||
return <AzureClientSecretsConnectionForm onSubmit={onSubmit} projectId={projectId} />;
|
||||
case AppConnection.AzureDevOps:
|
||||
return <AzureDevOpsConnectionForm onSubmit={onSubmit} />;
|
||||
return <AzureDevOpsConnectionForm onSubmit={onSubmit} projectId={projectId} />;
|
||||
case AppConnection.Windmill:
|
||||
return <WindmillConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Auth0:
|
||||
@@ -142,7 +148,7 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
case AppConnection.Flyio:
|
||||
return <FlyioConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.GitLab:
|
||||
return <GitLabConnectionForm onSubmit={onSubmit} />;
|
||||
return <GitLabConnectionForm onSubmit={onSubmit} projectId={projectId} />;
|
||||
case AppConnection.Cloudflare:
|
||||
return <CloudflareConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Bitbucket:
|
||||
@@ -200,16 +206,33 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
case AppConnection.AWS:
|
||||
return <AwsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
case AppConnection.GitHub:
|
||||
return <GitHubConnectionForm appConnection={appConnection} />;
|
||||
return (
|
||||
<GitHubConnectionForm appConnection={appConnection} projectId={appConnection.projectId} />
|
||||
);
|
||||
case AppConnection.GitHubRadar:
|
||||
return <GitHubRadarConnectionForm appConnection={appConnection} />;
|
||||
return (
|
||||
<GitHubRadarConnectionForm
|
||||
appConnection={appConnection}
|
||||
projectId={appConnection.projectId}
|
||||
/>
|
||||
);
|
||||
case AppConnection.GCP:
|
||||
return <GcpConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureKeyVault:
|
||||
return <AzureKeyVaultConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
return (
|
||||
<AzureKeyVaultConnectionForm
|
||||
appConnection={appConnection}
|
||||
onSubmit={onSubmit}
|
||||
projectId={appConnection.projectId}
|
||||
/>
|
||||
);
|
||||
case AppConnection.AzureAppConfiguration:
|
||||
return (
|
||||
<AzureAppConfigurationConnectionForm appConnection={appConnection} onSubmit={onSubmit} />
|
||||
<AzureAppConfigurationConnectionForm
|
||||
appConnection={appConnection}
|
||||
onSubmit={onSubmit}
|
||||
projectId={appConnection.projectId}
|
||||
/>
|
||||
);
|
||||
case AppConnection.AzureADCS:
|
||||
return <AzureADCSConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
@@ -232,9 +255,21 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
case AppConnection.Camunda:
|
||||
return <CamundaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.AzureClientSecrets:
|
||||
return <AzureClientSecretsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
return (
|
||||
<AzureClientSecretsConnectionForm
|
||||
appConnection={appConnection}
|
||||
onSubmit={onSubmit}
|
||||
projectId={appConnection.projectId}
|
||||
/>
|
||||
);
|
||||
case AppConnection.AzureDevOps:
|
||||
return <AzureDevOpsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
return (
|
||||
<AzureDevOpsConnectionForm
|
||||
appConnection={appConnection}
|
||||
onSubmit={onSubmit}
|
||||
projectId={appConnection.projectId}
|
||||
/>
|
||||
);
|
||||
case AppConnection.Windmill:
|
||||
return <WindmillConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Auth0:
|
||||
@@ -256,7 +291,13 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
case AppConnection.Flyio:
|
||||
return <FlyioConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.GitLab:
|
||||
return <GitLabConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
return (
|
||||
<GitLabConnectionForm
|
||||
onSubmit={onSubmit}
|
||||
appConnection={appConnection}
|
||||
projectId={appConnection.projectId}
|
||||
/>
|
||||
);
|
||||
case AppConnection.Cloudflare:
|
||||
return <CloudflareConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Bitbucket:
|
||||
@@ -278,12 +319,12 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
type Props = { onBack?: () => void } & Pick<FormProps, "onComplete"> &
|
||||
type Props = { onBack?: () => void; projectId?: string } & Pick<FormProps, "onComplete"> &
|
||||
(
|
||||
| { app: AppConnection; appConnection?: undefined }
|
||||
| { app?: undefined; appConnection: TAppConnection }
|
||||
);
|
||||
export const AppConnectionForm = ({ onBack, ...props }: Props) => {
|
||||
export const AppConnectionForm = ({ onBack, projectId, ...props }: Props) => {
|
||||
const { app, appConnection } = props;
|
||||
|
||||
return (
|
||||
@@ -296,7 +337,7 @@ export const AppConnectionForm = ({ onBack, ...props }: Props) => {
|
||||
{appConnection ? (
|
||||
<UpdateForm {...props} appConnection={appConnection} />
|
||||
) : (
|
||||
<CreateForm {...props} app={app} />
|
||||
<CreateForm {...props} app={app} projectId={projectId} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
+20
-3
@@ -6,7 +6,11 @@ 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 {
|
||||
APP_CONNECTION_MAP,
|
||||
getAppConnectionMethodDetails,
|
||||
useGetAppConnectionOauthReturnUrl
|
||||
} from "@app/helpers/appConnections";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import {
|
||||
AzureAppConfigurationConnectionMethod,
|
||||
@@ -15,6 +19,7 @@ import {
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import { AzureAppConfigurationFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
@@ -25,6 +30,7 @@ type ClientSecretForm = z.infer<typeof clientSecretSchema>;
|
||||
type Props = {
|
||||
appConnection?: TAzureAppConfigurationConnection;
|
||||
onSubmit: (formData: ClientSecretForm) => Promise<void>;
|
||||
projectId: string | undefined | null;
|
||||
};
|
||||
|
||||
const baseSchema = genericAppConnectionFieldsSchema.extend({
|
||||
@@ -96,7 +102,11 @@ const getDefaultValues = (appConnection?: TAzureAppConfigurationConnection): Par
|
||||
return base;
|
||||
};
|
||||
|
||||
export const AzureAppConfigurationConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
export const AzureAppConfigurationConnectionForm = ({
|
||||
appConnection,
|
||||
onSubmit,
|
||||
projectId
|
||||
}: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -110,6 +120,8 @@ export const AzureAppConfigurationConnectionForm = ({ appConnection, onSubmit }:
|
||||
defaultValues: getDefaultValues(appConnection)
|
||||
});
|
||||
|
||||
const returnUrl = useGetAppConnectionOauthReturnUrl();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
@@ -128,7 +140,12 @@ export const AzureAppConfigurationConnectionForm = ({ appConnection, onSubmit }:
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureAppConfigurationConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
JSON.stringify({
|
||||
...formData,
|
||||
connectionId: appConnection?.id,
|
||||
projectId,
|
||||
returnUrl
|
||||
} as AzureAppConfigurationFormData)
|
||||
);
|
||||
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`
|
||||
|
||||
+16
-3
@@ -7,7 +7,11 @@ 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 {
|
||||
APP_CONNECTION_MAP,
|
||||
getAppConnectionMethodDetails,
|
||||
useGetAppConnectionOauthReturnUrl
|
||||
} from "@app/helpers/appConnections";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import {
|
||||
AzureClientSecretsConnectionMethod,
|
||||
@@ -16,6 +20,7 @@ import {
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import { AzureClientSecretsFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
@@ -26,6 +31,7 @@ type ClientSecretForm = z.infer<typeof clientSecretSchema>;
|
||||
type Props = {
|
||||
appConnection?: TAzureClientSecretsConnection;
|
||||
onSubmit: (formData: ClientSecretForm) => Promise<void>;
|
||||
projectId: string | undefined | null;
|
||||
};
|
||||
|
||||
const baseSchema = genericAppConnectionFieldsSchema.extend({
|
||||
@@ -97,7 +103,7 @@ const getDefaultValues = (appConnection?: TAzureClientSecretsConnection): Partia
|
||||
return base;
|
||||
};
|
||||
|
||||
export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit, projectId }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -111,6 +117,8 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr
|
||||
defaultValues: getDefaultValues(appConnection)
|
||||
});
|
||||
|
||||
const returnUrl = useGetAppConnectionOauthReturnUrl();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
@@ -129,7 +137,12 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureClientSecretsConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
JSON.stringify({
|
||||
...formData,
|
||||
connectionId: appConnection?.id,
|
||||
projectId,
|
||||
returnUrl
|
||||
} as AzureClientSecretsFormData)
|
||||
);
|
||||
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://graph.microsoft.com/.default%20openid%20offline_access&state=${state}<:>azure-client-secrets`
|
||||
|
||||
+16
-3
@@ -7,7 +7,11 @@ 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 {
|
||||
APP_CONNECTION_MAP,
|
||||
getAppConnectionMethodDetails,
|
||||
useGetAppConnectionOauthReturnUrl
|
||||
} from "@app/helpers/appConnections";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import {
|
||||
AzureDevOpsConnectionMethod,
|
||||
@@ -16,6 +20,7 @@ import {
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import { AzureDevOpsFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
@@ -65,6 +70,7 @@ type OnSubmitForm = z.infer<typeof accessTokenSchema> | z.infer<typeof clientSec
|
||||
type Props = {
|
||||
appConnection?: TAzureDevOpsConnection;
|
||||
onSubmit: (formData: OnSubmitForm) => Promise<void>;
|
||||
projectId: string | undefined | null;
|
||||
};
|
||||
|
||||
const getDefaultValues = (appConnection?: TAzureDevOpsConnection): Partial<FormData> => {
|
||||
@@ -132,7 +138,7 @@ const getDefaultValues = (appConnection?: TAzureDevOpsConnection): Partial<FormD
|
||||
return base;
|
||||
};
|
||||
|
||||
export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit, projectId }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -146,6 +152,8 @@ export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit }: Props) =>
|
||||
defaultValues: getDefaultValues(appConnection)
|
||||
});
|
||||
|
||||
const returnUrl = useGetAppConnectionOauthReturnUrl();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
@@ -164,7 +172,12 @@ export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit }: Props) =>
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureDevOpsConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
JSON.stringify({
|
||||
...formData,
|
||||
connectionId: appConnection?.id,
|
||||
projectId,
|
||||
returnUrl
|
||||
} as AzureDevOpsFormData)
|
||||
);
|
||||
|
||||
window.location.assign(
|
||||
|
||||
+16
-3
@@ -6,7 +6,11 @@ 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 {
|
||||
APP_CONNECTION_MAP,
|
||||
getAppConnectionMethodDetails,
|
||||
useGetAppConnectionOauthReturnUrl
|
||||
} 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";
|
||||
@@ -15,6 +19,7 @@ import {
|
||||
TAzureKeyVaultConnection
|
||||
} from "@app/hooks/api/appConnections/types/azure-key-vault-connection";
|
||||
|
||||
import { AzureKeyVaultFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
@@ -25,6 +30,7 @@ type ClientSecretForm = z.infer<typeof clientSecretSchema>;
|
||||
type Props = {
|
||||
appConnection?: TAzureKeyVaultConnection;
|
||||
onSubmit: (formData: ClientSecretForm) => Promise<void>;
|
||||
projectId: string | undefined | null;
|
||||
};
|
||||
|
||||
const baseSchema = genericAppConnectionFieldsSchema.extend({
|
||||
@@ -96,7 +102,7 @@ const getDefaultValues = (appConnection?: TAzureKeyVaultConnection): Partial<For
|
||||
return base;
|
||||
};
|
||||
|
||||
export const AzureKeyVaultConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
export const AzureKeyVaultConnectionForm = ({ appConnection, onSubmit, projectId }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -110,6 +116,8 @@ export const AzureKeyVaultConnectionForm = ({ appConnection, onSubmit }: Props)
|
||||
defaultValues: getDefaultValues(appConnection)
|
||||
});
|
||||
|
||||
const returnUrl = useGetAppConnectionOauthReturnUrl();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
@@ -129,7 +137,12 @@ export const AzureKeyVaultConnectionForm = ({ appConnection, onSubmit }: Props)
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureKeyVaultConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
JSON.stringify({
|
||||
...formData,
|
||||
connectionId: appConnection?.id,
|
||||
projectId,
|
||||
returnUrl
|
||||
} as AzureKeyVaultFormData)
|
||||
);
|
||||
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`
|
||||
|
||||
+17
-3
@@ -25,7 +25,11 @@ import {
|
||||
OrgGatewayPermissionActions,
|
||||
OrgPermissionSubjects
|
||||
} from "@app/context/OrgPermissionContext/types";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import {
|
||||
APP_CONNECTION_MAP,
|
||||
getAppConnectionMethodDetails,
|
||||
useGetAppConnectionOauthReturnUrl
|
||||
} from "@app/helpers/appConnections";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import { gatewaysQueryKeys } from "@app/hooks/api";
|
||||
import {
|
||||
@@ -35,6 +39,7 @@ import {
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import { GithubFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
@@ -42,6 +47,7 @@ import {
|
||||
|
||||
type Props = {
|
||||
appConnection?: TGitHubConnection;
|
||||
projectId: string | undefined | null;
|
||||
};
|
||||
|
||||
const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
@@ -63,7 +69,7 @@ const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -98,13 +104,21 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
const selectedMethod = watch("method");
|
||||
const instanceType = watch("credentials.instanceType");
|
||||
|
||||
const returnUrl = useGetAppConnectionOauthReturnUrl();
|
||||
|
||||
const onSubmit = (formData: FormData) => {
|
||||
setIsRedirecting(true);
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"githubConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
JSON.stringify({
|
||||
...formData,
|
||||
credentials: formData.credentials as TGitHubConnection["credentials"],
|
||||
connectionId: appConnection?.id,
|
||||
projectId,
|
||||
returnUrl
|
||||
} as GithubFormData)
|
||||
);
|
||||
|
||||
const githubHost =
|
||||
|
||||
+16
-3
@@ -6,7 +6,11 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import {
|
||||
APP_CONNECTION_MAP,
|
||||
getAppConnectionMethodDetails,
|
||||
useGetAppConnectionOauthReturnUrl
|
||||
} from "@app/helpers/appConnections";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import {
|
||||
GitHubRadarConnectionMethod,
|
||||
@@ -15,6 +19,7 @@ import {
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import { GithubRadarFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
@@ -22,6 +27,7 @@ import {
|
||||
|
||||
type Props = {
|
||||
appConnection?: TGitHubRadarConnection;
|
||||
projectId: string | undefined | null;
|
||||
};
|
||||
|
||||
const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
@@ -31,7 +37,7 @@ const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const GitHubRadarConnectionForm = ({ appConnection }: Props) => {
|
||||
export const GitHubRadarConnectionForm = ({ appConnection, projectId }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -48,6 +54,8 @@ export const GitHubRadarConnectionForm = ({ appConnection }: Props) => {
|
||||
}
|
||||
});
|
||||
|
||||
const returnUrl = useGetAppConnectionOauthReturnUrl();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
@@ -63,7 +71,12 @@ export const GitHubRadarConnectionForm = ({ appConnection }: Props) => {
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"githubRadarConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
JSON.stringify({
|
||||
...formData,
|
||||
connectionId: appConnection?.id,
|
||||
projectId,
|
||||
returnUrl
|
||||
} as GithubRadarFormData)
|
||||
);
|
||||
|
||||
switch (formData.method) {
|
||||
|
||||
+14
-4
@@ -16,7 +16,11 @@ import {
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import {
|
||||
APP_CONNECTION_MAP,
|
||||
getAppConnectionMethodDetails,
|
||||
useGetAppConnectionOauthReturnUrl
|
||||
} 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";
|
||||
@@ -26,6 +30,7 @@ import {
|
||||
TGitLabConnection
|
||||
} from "@app/hooks/api/appConnections/types/gitlab-connection";
|
||||
|
||||
import { GitLabFormData } from "../../../OauthCallbackPage/OauthCallbackPage.types";
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
@@ -34,6 +39,7 @@ import {
|
||||
type Props = {
|
||||
appConnection?: TGitLabConnection;
|
||||
onSubmit: (formData: FormData) => Promise<void>;
|
||||
projectId: string | undefined | null;
|
||||
};
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
@@ -72,7 +78,7 @@ const formSchema = z.discriminatedUnion("method", [
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Props) => {
|
||||
export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit, projectId }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -98,6 +104,8 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
|
||||
} as FormData))
|
||||
});
|
||||
|
||||
const returnUrl = useGetAppConnectionOauthReturnUrl();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
@@ -132,8 +140,10 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
|
||||
JSON.stringify({
|
||||
...formData,
|
||||
connectionId: appConnection?.id,
|
||||
isUpdate
|
||||
})
|
||||
isUpdate,
|
||||
projectId,
|
||||
returnUrl
|
||||
} as GitLabFormData)
|
||||
);
|
||||
|
||||
// Redirect to Gitlab OAuth
|
||||
|
||||
+4
-2
@@ -9,14 +9,16 @@ import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
|
||||
import { useAppConnectionOptions } from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
type Props = {
|
||||
onSelect: (app: AppConnection) => void;
|
||||
projectType?: ProjectType;
|
||||
};
|
||||
|
||||
export const AppConnectionsSelect = ({ onSelect }: Props) => {
|
||||
export const AppConnectionsSelect = ({ onSelect, projectType }: Props) => {
|
||||
const { subscription } = useSubscription();
|
||||
const { isPending, data: appConnectionOptions } = useAppConnectionOptions();
|
||||
const { isPending, data: appConnectionOptions } = useAppConnectionOptions(projectType);
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||
|
||||
|
||||
+127
-44
@@ -1,19 +1,23 @@
|
||||
import { useCallback } from "react";
|
||||
import { subject } from "@casl/ability";
|
||||
import {
|
||||
faAsterisk,
|
||||
faBuilding,
|
||||
faCheck,
|
||||
faCopy,
|
||||
faEdit,
|
||||
faEllipsisV,
|
||||
faInfoCircle,
|
||||
faServer,
|
||||
faTable,
|
||||
faTrash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { VariablePermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Badge,
|
||||
DropdownMenu,
|
||||
@@ -25,9 +29,11 @@ import {
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionSubjects } from "@app/context";
|
||||
import { OrgPermissionSubjects, ProjectPermissionSub } from "@app/context";
|
||||
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import { getProjectBaseURL } from "@app/helpers/project";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { TAppConnection } from "@app/hooks/api/appConnections";
|
||||
|
||||
@@ -36,15 +42,18 @@ type Props = {
|
||||
onDelete: (appConnection: TAppConnection) => void;
|
||||
onEditCredentials: (appConnection: TAppConnection) => void;
|
||||
onEditDetails: (appConnection: TAppConnection) => void;
|
||||
isProjectView: boolean;
|
||||
};
|
||||
|
||||
export const AppConnectionRow = ({
|
||||
appConnection,
|
||||
onDelete,
|
||||
onEditCredentials,
|
||||
onEditDetails
|
||||
onEditDetails,
|
||||
isProjectView
|
||||
}: Props) => {
|
||||
const { id, name, method, app, description, isPlatformManagedCredentials } = appConnection;
|
||||
const { id, name, method, app, description, isPlatformManagedCredentials, project } =
|
||||
appConnection;
|
||||
|
||||
const [isIdCopied, setIsIdCopied] = useToggle(false);
|
||||
|
||||
@@ -111,7 +120,38 @@ export const AppConnectionRow = ({
|
||||
{methodDetails.name}
|
||||
</p>
|
||||
</Td>
|
||||
|
||||
{!isProjectView && (
|
||||
<Td className="!min-w-[8rem] max-w-0">
|
||||
{project ? (
|
||||
<Link
|
||||
// @ts-expect-error app-connections aren't in kms/ssh
|
||||
to={`${getProjectBaseURL(project.type)}/app-connections`}
|
||||
params={{
|
||||
projectId: project.id
|
||||
}}
|
||||
className="underline"
|
||||
>
|
||||
<p className="truncate">
|
||||
<FontAwesomeIcon
|
||||
size="sm"
|
||||
className="mr-1.5 text-mineshaft-300/75"
|
||||
icon={faTable}
|
||||
/>
|
||||
{project.name}
|
||||
</p>
|
||||
</Link>
|
||||
) : (
|
||||
<p className="truncate">
|
||||
<FontAwesomeIcon
|
||||
size="sm"
|
||||
className="mr-1.5 text-mineshaft-300/75"
|
||||
icon={faBuilding}
|
||||
/>
|
||||
Organization
|
||||
</p>
|
||||
)}
|
||||
</Td>
|
||||
)}
|
||||
<Td>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{isPlatformManagedCredentials && (
|
||||
@@ -143,48 +183,91 @@ export const AppConnectionRow = ({
|
||||
>
|
||||
Copy Connection ID
|
||||
</DropdownMenuItem>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionAppConnectionActions.Edit}
|
||||
a={OrgPermissionSubjects.AppConnections}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faEdit} />}
|
||||
onClick={() => onEditDetails(appConnection)}
|
||||
{(isProjectView || !project) && (
|
||||
<>
|
||||
<VariablePermissionCan
|
||||
type={isProjectView ? "project" : "org"}
|
||||
I={
|
||||
isProjectView
|
||||
? ProjectPermissionAppConnectionActions.Edit
|
||||
: OrgPermissionAppConnectionActions.Edit
|
||||
}
|
||||
a={
|
||||
isProjectView
|
||||
? subject(ProjectPermissionSub.AppConnections, {
|
||||
connectionId: id
|
||||
})
|
||||
: subject(OrgPermissionSubjects.AppConnections, {
|
||||
connectionId: id
|
||||
})
|
||||
}
|
||||
>
|
||||
Edit Details
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionAppConnectionActions.Edit}
|
||||
a={OrgPermissionSubjects.AppConnections}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faAsterisk} />}
|
||||
onClick={() => onEditCredentials(appConnection)}
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faEdit} />}
|
||||
onClick={() => onEditDetails(appConnection)}
|
||||
>
|
||||
Edit Details
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</VariablePermissionCan>
|
||||
<VariablePermissionCan
|
||||
type={isProjectView ? "project" : "org"}
|
||||
I={
|
||||
isProjectView
|
||||
? ProjectPermissionAppConnectionActions.Edit
|
||||
: OrgPermissionAppConnectionActions.Edit
|
||||
}
|
||||
a={
|
||||
isProjectView
|
||||
? subject(ProjectPermissionSub.AppConnections, {
|
||||
connectionId: id
|
||||
})
|
||||
: subject(OrgPermissionSubjects.AppConnections, {
|
||||
connectionId: id
|
||||
})
|
||||
}
|
||||
>
|
||||
{isPlatformManagedCredentials ? "View" : "Edit"} Credentials
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionAppConnectionActions.Delete}
|
||||
a={OrgPermissionSubjects.AppConnections}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||
onClick={() => onDelete(appConnection)}
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faAsterisk} />}
|
||||
onClick={() => onEditCredentials(appConnection)}
|
||||
>
|
||||
{isPlatformManagedCredentials ? "View" : "Edit"} Credentials
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</VariablePermissionCan>
|
||||
<VariablePermissionCan
|
||||
type={isProjectView ? "project" : "org"}
|
||||
I={
|
||||
isProjectView
|
||||
? ProjectPermissionAppConnectionActions.Delete
|
||||
: OrgPermissionAppConnectionActions.Delete
|
||||
}
|
||||
a={
|
||||
isProjectView
|
||||
? subject(ProjectPermissionSub.AppConnections, {
|
||||
connectionId: id
|
||||
})
|
||||
: subject(OrgPermissionSubjects.AppConnections, {
|
||||
connectionId: id
|
||||
})
|
||||
}
|
||||
>
|
||||
Delete Connection
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||
onClick={() => onDelete(appConnection)}
|
||||
>
|
||||
Delete Connection
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</VariablePermissionCan>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Tooltip>
|
||||
|
||||
+150
-6
@@ -2,16 +2,21 @@ import { useMemo, useState } from "react";
|
||||
import {
|
||||
faArrowDown,
|
||||
faArrowUp,
|
||||
faArrowUpRightFromSquare,
|
||||
faBookOpen,
|
||||
faCheckCircle,
|
||||
faFilter,
|
||||
faMagnifyingGlass,
|
||||
faPlug,
|
||||
faPlus,
|
||||
faSearch
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { VariablePermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@@ -29,6 +34,9 @@ import {
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { OrgPermissionSubjects, ProjectPermissionSub } from "@app/context";
|
||||
import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import {
|
||||
getUserTablePreference,
|
||||
@@ -39,7 +47,9 @@ import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
|
||||
import { TAppConnection, useListAppConnections } from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { AddAppConnectionModal } from "./AddAppConnectionModal";
|
||||
import { AppConnectionRow } from "./AppConnectionRow";
|
||||
import { DeleteAppConnectionModal } from "./DeleteAppConnectionModal";
|
||||
import { EditAppConnectionCredentialsModal } from "./EditAppConnectionCredentialsModal";
|
||||
@@ -48,22 +58,45 @@ import { EditAppConnectionDetailsModal } from "./EditAppConnectionDetailsModal";
|
||||
enum AppConnectionsOrderBy {
|
||||
App = "app",
|
||||
Name = "name",
|
||||
Method = "method"
|
||||
Method = "method",
|
||||
ManagedBy = "managed-by"
|
||||
}
|
||||
|
||||
type AppConnectionFilters = {
|
||||
apps: AppConnection[];
|
||||
};
|
||||
|
||||
export const AppConnectionsTable = () => {
|
||||
const { isPending, data: appConnections = [] } = useListAppConnections();
|
||||
enum View {
|
||||
All = "all",
|
||||
Scope = "scope"
|
||||
}
|
||||
|
||||
const APP_CONNECTION_VIEW_STORAGE_KEY = "app-connection-view";
|
||||
|
||||
type Props = {
|
||||
projectId?: string;
|
||||
projectType?: ProjectType;
|
||||
};
|
||||
|
||||
export const AppConnectionsTable = ({ projectId, projectType }: Props) => {
|
||||
const isProjectView = Boolean(projectId);
|
||||
const { isPending, data: appConnections = [] } = useListAppConnections(projectId);
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"addConnection",
|
||||
"deleteConnection",
|
||||
"editCredentials",
|
||||
"editDetails"
|
||||
] as const);
|
||||
|
||||
const [view, setView] = useState<View>(() => {
|
||||
const storedView = localStorage.getItem(APP_CONNECTION_VIEW_STORAGE_KEY) as View | null;
|
||||
|
||||
if (storedView && Object.values(View).includes(storedView)) return storedView;
|
||||
|
||||
return View.Scope;
|
||||
});
|
||||
|
||||
const [filters, setFilters] = useState<AppConnectionFilters>({
|
||||
apps: []
|
||||
});
|
||||
@@ -96,6 +129,10 @@ export const AppConnectionsTable = () => {
|
||||
.filter((appConnection) => {
|
||||
const { app, method, name } = appConnection;
|
||||
|
||||
if (view === View.Scope && !isProjectView && appConnection.projectId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filters.apps.length && !filters.apps.includes(app)) return false;
|
||||
|
||||
const searchValue = search.trim().toLowerCase();
|
||||
@@ -121,6 +158,13 @@ export const AppConnectionsTable = () => {
|
||||
.localeCompare(
|
||||
getAppConnectionMethodDetails(connectionTwo.method).name.toLowerCase()
|
||||
);
|
||||
case AppConnectionsOrderBy.ManagedBy:
|
||||
if (!connectionOne.project) return 1;
|
||||
if (!connectionTwo.project) return -1;
|
||||
|
||||
return connectionOne.project.name
|
||||
.toLowerCase()
|
||||
.localeCompare(connectionTwo.project.name.toLowerCase());
|
||||
case AppConnectionsOrderBy.App:
|
||||
default:
|
||||
return APP_CONNECTION_MAP[connectionOne.app].name
|
||||
@@ -128,7 +172,7 @@ export const AppConnectionsTable = () => {
|
||||
.localeCompare(APP_CONNECTION_MAP[connectionTwo.app].name.toLowerCase());
|
||||
}
|
||||
}),
|
||||
[appConnections, orderDirection, search, orderBy, filters]
|
||||
[appConnections, orderDirection, search, orderBy, filters, view]
|
||||
);
|
||||
|
||||
useResetPageHelper({
|
||||
@@ -165,8 +209,87 @@ export const AppConnectionsTable = () => {
|
||||
handlePopUpOpen("editDetails", appConnection);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<div className="flex items-center 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.12rem] 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 your project
|
||||
{isProjectView ? "" : "s"}.
|
||||
</p>
|
||||
</div>
|
||||
<VariablePermissionCan
|
||||
type={isProjectView ? "project" : "org"}
|
||||
I={
|
||||
isProjectView
|
||||
? ProjectPermissionAppConnectionActions.Create
|
||||
: OrgPermissionAppConnectionActions.Create
|
||||
}
|
||||
a={
|
||||
isProjectView
|
||||
? ProjectPermissionSub.AppConnections
|
||||
: OrgPermissionSubjects.AppConnections
|
||||
}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("addConnection")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add Connection
|
||||
</Button>
|
||||
)}
|
||||
</VariablePermissionCan>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{!isProjectView && (
|
||||
<div className="flex gap-x-0.5 rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1">
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
setView(View.Scope);
|
||||
localStorage.setItem(APP_CONNECTION_VIEW_STORAGE_KEY, View.Scope);
|
||||
}}
|
||||
size="xs"
|
||||
className={`${
|
||||
view === View.Scope ? "bg-mineshaft-500" : "bg-transparent"
|
||||
} min-w-[5rem] rounded border-none hover:bg-mineshaft-600`}
|
||||
>
|
||||
Organization
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
setView(View.All);
|
||||
localStorage.setItem(APP_CONNECTION_VIEW_STORAGE_KEY, View.All);
|
||||
}}
|
||||
size="xs"
|
||||
className={`${
|
||||
view === View.All ? "bg-mineshaft-500" : "bg-transparent"
|
||||
} min-w-[5rem] rounded border-none hover:bg-mineshaft-600`}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
@@ -269,7 +392,21 @@ export const AppConnectionsTable = () => {
|
||||
</IconButton>
|
||||
</div>
|
||||
</Th>
|
||||
|
||||
{!isProjectView && (
|
||||
<Th>
|
||||
<div className="flex items-center">
|
||||
Managed By
|
||||
<IconButton
|
||||
variant="plain"
|
||||
className={getClassName(AppConnectionsOrderBy.ManagedBy)}
|
||||
ariaLabel="sort"
|
||||
onClick={() => handleSort(AppConnectionsOrderBy.ManagedBy)}
|
||||
>
|
||||
<FontAwesomeIcon icon={getColSortIcon(AppConnectionsOrderBy.ManagedBy)} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Th>
|
||||
)}
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
@@ -284,6 +421,7 @@ export const AppConnectionsTable = () => {
|
||||
onDelete={handleDelete}
|
||||
onEditCredentials={handleEditCredentials}
|
||||
onEditDetails={handleEditDetails}
|
||||
isProjectView={isProjectView}
|
||||
/>
|
||||
))}
|
||||
</TBody>
|
||||
@@ -323,6 +461,12 @@ export const AppConnectionsTable = () => {
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("editDetails", isOpen)}
|
||||
appConnection={popUp.editDetails.data}
|
||||
/>
|
||||
<AddAppConnectionModal
|
||||
isOpen={popUp.addConnection.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addConnection", isOpen)}
|
||||
projectId={projectId}
|
||||
projectType={projectType}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+127
-111
@@ -12,73 +12,14 @@ import {
|
||||
AzureKeyVaultConnectionMethod,
|
||||
GitHubConnectionMethod,
|
||||
GitLabConnectionMethod,
|
||||
TAzureAppConfigurationConnection,
|
||||
TAzureClientSecretsConnection,
|
||||
TAzureDevOpsConnection,
|
||||
TAzureKeyVaultConnection,
|
||||
TGitHubConnection,
|
||||
TGitHubRadarConnection,
|
||||
TGitLabConnection,
|
||||
TAppConnection,
|
||||
useCreateAppConnection,
|
||||
useUpdateAppConnection
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
type BaseFormData = {
|
||||
returnUrl?: string;
|
||||
connectionId?: string;
|
||||
isUpdate?: boolean;
|
||||
};
|
||||
|
||||
type GithubFormData = BaseFormData &
|
||||
Pick<TGitHubConnection, "name" | "method" | "description" | "gatewayId" | "credentials">;
|
||||
|
||||
type GithubRadarFormData = BaseFormData &
|
||||
Pick<TGitHubRadarConnection, "name" | "method" | "description">;
|
||||
|
||||
type GitLabFormData = BaseFormData &
|
||||
Pick<TGitLabConnection, "name" | "method" | "description" | "credentials">;
|
||||
|
||||
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 AzureClientSecretsFormData = BaseFormData &
|
||||
Pick<TAzureClientSecretsConnection, "name" | "method" | "description"> &
|
||||
Pick<TAzureClientSecretsConnection["credentials"], "tenantId">;
|
||||
|
||||
type OAuthCredentials = Extract<
|
||||
TAzureDevOpsConnection,
|
||||
{ method: AzureDevOpsConnectionMethod.OAuth }
|
||||
>["credentials"];
|
||||
type AccessTokenCredentials = Extract<
|
||||
TAzureDevOpsConnection,
|
||||
{ method: AzureDevOpsConnectionMethod.AccessToken }
|
||||
>["credentials"];
|
||||
|
||||
type AzureDevOpsFormData = BaseFormData &
|
||||
Pick<TAzureDevOpsConnection, "name" | "method" | "description"> &
|
||||
(Pick<OAuthCredentials, "tenantId" | "orgName"> | Pick<AccessTokenCredentials, "orgName">);
|
||||
|
||||
type FormDataMap = {
|
||||
[AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub };
|
||||
[AppConnection.GitHubRadar]: GithubRadarFormData & { app: AppConnection.GitHubRadar };
|
||||
[AppConnection.GitLab]: GitLabFormData & { app: AppConnection.GitLab };
|
||||
[AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault };
|
||||
[AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & {
|
||||
app: AppConnection.AzureAppConfiguration;
|
||||
};
|
||||
[AppConnection.AzureClientSecrets]: AzureClientSecretsFormData & {
|
||||
app: AppConnection.AzureClientSecrets;
|
||||
};
|
||||
[AppConnection.AzureDevOps]: AzureDevOpsFormData & {
|
||||
app: AppConnection.AzureDevOps;
|
||||
};
|
||||
};
|
||||
import { FormDataMap } from "./OauthCallbackPage.types";
|
||||
|
||||
const formDataStorageFieldMap: Partial<Record<AppConnection, string>> = {
|
||||
[AppConnection.GitHub]: "githubConnectionFormData",
|
||||
@@ -148,11 +89,14 @@ export const OAuthCallbackPage = () => {
|
||||
|
||||
clearState(AppConnection.GitLab);
|
||||
|
||||
const { connectionId, name, description, returnUrl, isUpdate, credentials } = formData;
|
||||
const { connectionId, name, description, returnUrl, isUpdate, projectId, credentials } =
|
||||
formData;
|
||||
|
||||
let connection: TAppConnection;
|
||||
|
||||
try {
|
||||
if (isUpdate && connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
connection = await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.GitLab,
|
||||
connectionId,
|
||||
credentials: {
|
||||
@@ -161,10 +105,11 @@ export const OAuthCallbackPage = () => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
connection = await createAppConnection.mutateAsync({
|
||||
app: AppConnection.GitLab,
|
||||
name,
|
||||
description,
|
||||
projectId,
|
||||
method: GitLabConnectionMethod.OAuth,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
@@ -173,14 +118,12 @@ export const OAuthCallbackPage = () => {
|
||||
});
|
||||
}
|
||||
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
});
|
||||
|
||||
return {
|
||||
connectionId,
|
||||
returnUrl,
|
||||
appConnectionName: formData.app
|
||||
appConnectionName: formData.app,
|
||||
projectId,
|
||||
connection
|
||||
};
|
||||
} catch (err: any) {
|
||||
createNotification({
|
||||
@@ -189,7 +132,10 @@ export const OAuthCallbackPage = () => {
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
to: returnUrl,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@@ -201,11 +147,13 @@ export const OAuthCallbackPage = () => {
|
||||
|
||||
clearState(AppConnection.AzureKeyVault);
|
||||
|
||||
const { connectionId, name, description, returnUrl } = formData;
|
||||
const { connectionId, name, description, returnUrl, projectId } = formData;
|
||||
|
||||
let connection: TAppConnection;
|
||||
|
||||
try {
|
||||
if (connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
connection = await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureKeyVault,
|
||||
connectionId,
|
||||
credentials: {
|
||||
@@ -214,10 +162,11 @@ export const OAuthCallbackPage = () => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
connection = await createAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureKeyVault,
|
||||
name,
|
||||
description,
|
||||
projectId,
|
||||
method: AzureKeyVaultConnectionMethod.OAuth,
|
||||
credentials: {
|
||||
tenantId: formData.tenantId,
|
||||
@@ -232,14 +181,20 @@ export const OAuthCallbackPage = () => {
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
to: returnUrl,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
connectionId,
|
||||
returnUrl,
|
||||
appConnectionName: formData.app
|
||||
appConnectionName: formData.app,
|
||||
projectId,
|
||||
connection
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -249,11 +204,13 @@ export const OAuthCallbackPage = () => {
|
||||
|
||||
clearState(AppConnection.AzureAppConfiguration);
|
||||
|
||||
const { connectionId, name, description, returnUrl } = formData;
|
||||
const { connectionId, name, description, returnUrl, projectId } = formData;
|
||||
|
||||
let connection: TAppConnection;
|
||||
|
||||
try {
|
||||
if (connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
connection = await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureAppConfiguration,
|
||||
connectionId,
|
||||
credentials: {
|
||||
@@ -262,10 +219,11 @@ export const OAuthCallbackPage = () => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
connection = await createAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureAppConfiguration,
|
||||
name,
|
||||
description,
|
||||
projectId,
|
||||
method: AzureAppConfigurationConnectionMethod.OAuth,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
@@ -280,14 +238,20 @@ export const OAuthCallbackPage = () => {
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
to: returnUrl,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
connectionId,
|
||||
returnUrl,
|
||||
appConnectionName: formData.app
|
||||
appConnectionName: formData.app,
|
||||
projectId,
|
||||
connection
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -297,11 +261,13 @@ export const OAuthCallbackPage = () => {
|
||||
|
||||
clearState(AppConnection.AzureClientSecrets);
|
||||
|
||||
const { connectionId, name, description, returnUrl } = formData;
|
||||
const { connectionId, name, description, returnUrl, projectId } = formData;
|
||||
|
||||
let connection: TAppConnection;
|
||||
|
||||
try {
|
||||
if (connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
connection = await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureClientSecrets,
|
||||
connectionId,
|
||||
credentials: {
|
||||
@@ -310,11 +276,12 @@ export const OAuthCallbackPage = () => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
connection = await createAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureClientSecrets,
|
||||
name,
|
||||
description,
|
||||
method: AzureClientSecretsConnectionMethod.OAuth,
|
||||
projectId,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
tenantId: formData.tenantId
|
||||
@@ -328,14 +295,20 @@ export const OAuthCallbackPage = () => {
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
to: returnUrl,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
connectionId,
|
||||
returnUrl,
|
||||
appConnectionName: formData.app
|
||||
appConnectionName: formData.app,
|
||||
projectId,
|
||||
connection
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -345,7 +318,9 @@ export const OAuthCallbackPage = () => {
|
||||
|
||||
clearState(AppConnection.AzureDevOps);
|
||||
|
||||
const { connectionId, name, description, returnUrl } = formData;
|
||||
const { connectionId, name, description, returnUrl, projectId } = formData;
|
||||
|
||||
let connection: TAppConnection;
|
||||
|
||||
try {
|
||||
if (!("tenantId" in formData)) {
|
||||
@@ -353,7 +328,7 @@ export const OAuthCallbackPage = () => {
|
||||
}
|
||||
|
||||
if (connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
connection = await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureDevOps,
|
||||
connectionId,
|
||||
credentials: {
|
||||
@@ -363,11 +338,12 @@ export const OAuthCallbackPage = () => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
connection = await createAppConnection.mutateAsync({
|
||||
app: AppConnection.AzureDevOps,
|
||||
name,
|
||||
description,
|
||||
method: AzureDevOpsConnectionMethod.OAuth,
|
||||
projectId,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
tenantId: formData.tenantId as string,
|
||||
@@ -382,14 +358,20 @@ export const OAuthCallbackPage = () => {
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
to: returnUrl,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
connectionId,
|
||||
returnUrl,
|
||||
appConnectionName: formData.app
|
||||
appConnectionName: formData.app,
|
||||
projectId,
|
||||
connection
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -399,11 +381,14 @@ export const OAuthCallbackPage = () => {
|
||||
|
||||
clearState(AppConnection.GitHub);
|
||||
|
||||
const { connectionId, name, description, returnUrl, gatewayId, credentials } = formData;
|
||||
const { connectionId, name, description, returnUrl, gatewayId, credentials, projectId } =
|
||||
formData;
|
||||
|
||||
let connection: TAppConnection;
|
||||
|
||||
try {
|
||||
if (connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
connection = await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.GitHub,
|
||||
...(installationId
|
||||
? {
|
||||
@@ -427,10 +412,11 @@ export const OAuthCallbackPage = () => {
|
||||
})
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
connection = await createAppConnection.mutateAsync({
|
||||
app: AppConnection.GitHub,
|
||||
name,
|
||||
description,
|
||||
projectId,
|
||||
...(installationId
|
||||
? {
|
||||
method: GitHubConnectionMethod.App,
|
||||
@@ -460,14 +446,20 @@ export const OAuthCallbackPage = () => {
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
to: returnUrl,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
connectionId,
|
||||
returnUrl,
|
||||
appConnectionName: formData.app
|
||||
appConnectionName: formData.app,
|
||||
projectId,
|
||||
connection
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -477,11 +469,13 @@ export const OAuthCallbackPage = () => {
|
||||
|
||||
clearState(AppConnection.GitHubRadar);
|
||||
|
||||
const { connectionId, name, description, returnUrl } = formData;
|
||||
const { connectionId, name, description, returnUrl, projectId } = formData;
|
||||
|
||||
let connection: TAppConnection;
|
||||
|
||||
try {
|
||||
if (connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
connection = await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.GitHubRadar,
|
||||
connectionId,
|
||||
credentials: {
|
||||
@@ -490,11 +484,12 @@ export const OAuthCallbackPage = () => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
connection = await createAppConnection.mutateAsync({
|
||||
app: AppConnection.GitHubRadar,
|
||||
name,
|
||||
description,
|
||||
method: GitHubConnectionMethod.App,
|
||||
projectId,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
installationId: installationId as string
|
||||
@@ -508,14 +503,20 @@ export const OAuthCallbackPage = () => {
|
||||
type: "error"
|
||||
});
|
||||
navigate({
|
||||
to: returnUrl ?? "/organization/app-connections"
|
||||
to: returnUrl,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
connectionId,
|
||||
returnUrl,
|
||||
appConnectionName: formData.app
|
||||
appConnectionName: formData.app,
|
||||
projectId,
|
||||
connection
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -530,8 +531,13 @@ export const OAuthCallbackPage = () => {
|
||||
if (!isReady) return;
|
||||
|
||||
(async () => {
|
||||
let data: { connectionId?: string; returnUrl?: string; appConnectionName?: string } | null =
|
||||
null;
|
||||
let data: {
|
||||
returnUrl: string;
|
||||
appConnectionName: string;
|
||||
connectionId?: string;
|
||||
projectId?: string;
|
||||
connection: TAppConnection;
|
||||
} | null = null;
|
||||
|
||||
if (appConnection === AppConnection.GitHub) {
|
||||
data = await handleGithub();
|
||||
@@ -554,16 +560,26 @@ export const OAuthCallbackPage = () => {
|
||||
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,
|
||||
params: {
|
||||
projectId: data.projectId ?? undefined
|
||||
},
|
||||
// scott: if it's not an app connection page we need to pass connection details as it's an inline creation
|
||||
search: data.returnUrl.includes("app-connections")
|
||||
? undefined
|
||||
: {
|
||||
connectionId: data.connection.id,
|
||||
connectionName: data.connection.name,
|
||||
...(data.returnUrl.includes("integrations")
|
||||
? {
|
||||
selectedTab: IntegrationsListPageTabs.SecretSyncs
|
||||
}
|
||||
: {})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await navigate({
|
||||
to: data?.returnUrl ?? "/organization/app-connections"
|
||||
});
|
||||
})();
|
||||
}, [isReady]);
|
||||
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
AzureDevOpsConnectionMethod,
|
||||
TAzureAppConfigurationConnection,
|
||||
TAzureClientSecretsConnection,
|
||||
TAzureDevOpsConnection,
|
||||
TAzureKeyVaultConnection,
|
||||
TGitHubConnection,
|
||||
TGitHubRadarConnection,
|
||||
TGitLabConnection
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
type BaseFormData = {
|
||||
returnUrl: string;
|
||||
connectionId?: string;
|
||||
isUpdate?: boolean;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type GithubFormData = BaseFormData &
|
||||
Pick<TGitHubConnection, "name" | "method" | "description" | "gatewayId" | "credentials">;
|
||||
|
||||
export type GithubRadarFormData = BaseFormData &
|
||||
Pick<TGitHubRadarConnection, "name" | "method" | "description">;
|
||||
|
||||
export type GitLabFormData = BaseFormData &
|
||||
Pick<TGitLabConnection, "name" | "method" | "description" | "credentials">;
|
||||
|
||||
export type AzureKeyVaultFormData = BaseFormData &
|
||||
Pick<TAzureKeyVaultConnection, "name" | "method" | "description"> &
|
||||
Pick<TAzureKeyVaultConnection["credentials"], "tenantId">;
|
||||
|
||||
export type AzureAppConfigurationFormData = BaseFormData &
|
||||
Pick<TAzureAppConfigurationConnection, "name" | "method" | "description"> &
|
||||
Pick<TAzureAppConfigurationConnection["credentials"], "tenantId">;
|
||||
|
||||
export type AzureClientSecretsFormData = BaseFormData &
|
||||
Pick<TAzureClientSecretsConnection, "name" | "method" | "description"> &
|
||||
Pick<TAzureClientSecretsConnection["credentials"], "tenantId">;
|
||||
|
||||
type OAuthCredentials = Extract<
|
||||
TAzureDevOpsConnection,
|
||||
{ method: AzureDevOpsConnectionMethod.OAuth }
|
||||
>["credentials"];
|
||||
type AccessTokenCredentials = Extract<
|
||||
TAzureDevOpsConnection,
|
||||
{ method: AzureDevOpsConnectionMethod.AccessToken }
|
||||
>["credentials"];
|
||||
|
||||
export type AzureDevOpsFormData = BaseFormData &
|
||||
Pick<TAzureDevOpsConnection, "name" | "method" | "description"> &
|
||||
(Pick<OAuthCredentials, "tenantId" | "orgName"> | Pick<AccessTokenCredentials, "orgName">);
|
||||
|
||||
export type FormDataMap = {
|
||||
[AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub };
|
||||
[AppConnection.GitHubRadar]: GithubRadarFormData & { app: AppConnection.GitHubRadar };
|
||||
[AppConnection.GitLab]: GitLabFormData & { app: AppConnection.GitLab };
|
||||
[AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault };
|
||||
[AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & {
|
||||
app: AppConnection.AzureAppConfiguration;
|
||||
};
|
||||
[AppConnection.AzureClientSecrets]: AzureClientSecretsFormData & {
|
||||
app: AppConnection.AzureClientSecrets;
|
||||
};
|
||||
[AppConnection.AzureDevOps]: AzureDevOpsFormData & {
|
||||
app: AppConnection.AzureDevOps;
|
||||
};
|
||||
};
|
||||
@@ -4,11 +4,11 @@ import {
|
||||
faArrowUpRightFromSquare,
|
||||
faBookOpen,
|
||||
faCopy,
|
||||
faDoorClosed,
|
||||
faEdit,
|
||||
faEllipsisV,
|
||||
faInfoCircle,
|
||||
faMagnifyingGlass,
|
||||
faPlug,
|
||||
faSearch,
|
||||
faTrash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -243,7 +243,7 @@ export const GatewayListPage = withPermission(
|
||||
? "No Gateways match search..."
|
||||
: "No Gateways have been configured"
|
||||
}
|
||||
icon={gateways?.length ? faSearch : faPlug}
|
||||
icon={gateways?.length ? faSearch : faDoorClosed}
|
||||
/>
|
||||
)}
|
||||
<DeleteActionModal
|
||||
|
||||
Reference in New Issue
Block a user