mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 08:28:52 +00:00
refactor: deprecate native integrations in favor of secret syncs; update API routes and frontend components accordingly
This commit is contained in:
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApiDocsTags, INTEGRATION_AUTH } from "@app/lib/api-docs";
|
import { ApiDocsTags, INTEGRATION_AUTH } from "@app/lib/api-docs";
|
||||||
|
import { ForbiddenRequestError } from "@app/lib/errors";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -10,6 +11,9 @@ import { Integrations } from "@app/services/integration-auth/integration-list";
|
|||||||
|
|
||||||
import { integrationAuthPubSchema } from "../sanitizedSchemas";
|
import { integrationAuthPubSchema } from "../sanitizedSchemas";
|
||||||
|
|
||||||
|
const NATIVE_INTEGRATION_DEPRECATION_MESSAGE =
|
||||||
|
"We're moving Native Integrations to Secret Syncs. Check the documentation at https://infisical.com/docs/integrations/secret-syncs/overview. If the integration you need isn't available in the Secret Syncs, please get in touch with us at [email protected].";
|
||||||
|
|
||||||
export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) => {
|
export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -333,27 +337,33 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (_) => {
|
||||||
const integrationAuth = await server.services.integrationAuth.saveIntegrationToken({
|
throw new ForbiddenRequestError({
|
||||||
actorId: req.permission.id,
|
message: NATIVE_INTEGRATION_DEPRECATION_MESSAGE
|
||||||
actor: req.permission.type,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
projectId: req.body.workspaceId,
|
|
||||||
...req.body
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
// We are keeping the old response commented out for an easy revert on the API if we need to before the full phase out.
|
||||||
...req.auditLogInfo,
|
|
||||||
projectId: req.body.workspaceId,
|
// const integrationAuth = await server.services.integrationAuth.saveIntegrationToken({
|
||||||
event: {
|
// actorId: req.permission.id,
|
||||||
type: EventType.AUTHORIZE_INTEGRATION,
|
// actor: req.permission.type,
|
||||||
metadata: {
|
// actorAuthMethod: req.permission.authMethod,
|
||||||
integration: integrationAuth.integration
|
// actorOrgId: req.permission.orgId,
|
||||||
}
|
// projectId: req.body.workspaceId,
|
||||||
}
|
// ...req.body
|
||||||
});
|
// });
|
||||||
return { integrationAuth };
|
|
||||||
|
// await server.services.auditLog.createAuditLog({
|
||||||
|
// ...req.auditLogInfo,
|
||||||
|
// projectId: req.body.workspaceId,
|
||||||
|
// event: {
|
||||||
|
// type: EventType.AUTHORIZE_INTEGRATION,
|
||||||
|
// metadata: {
|
||||||
|
// integration: integrationAuth.integration
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// return { integrationAuth };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,19 @@ import { z } from "zod";
|
|||||||
import { IntegrationsSchema } from "@app/db/schemas";
|
import { IntegrationsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApiDocsTags, INTEGRATION } from "@app/lib/api-docs";
|
import { ApiDocsTags, INTEGRATION } from "@app/lib/api-docs";
|
||||||
|
import { ForbiddenRequestError } from "@app/lib/errors";
|
||||||
import { removeTrailingSlash, shake } from "@app/lib/fn";
|
import { removeTrailingSlash, shake } from "@app/lib/fn";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { IntegrationMetadataSchema } from "@app/services/integration/integration-schema";
|
import { IntegrationMetadataSchema } from "@app/services/integration/integration-schema";
|
||||||
import { Integrations } from "@app/services/integration-auth/integration-list";
|
import { Integrations } from "@app/services/integration-auth/integration-list";
|
||||||
import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types";
|
|
||||||
|
|
||||||
import {} from "../sanitizedSchemas";
|
import {} from "../sanitizedSchemas";
|
||||||
|
|
||||||
|
const NATIVE_INTEGRATION_DEPRECATION_MESSAGE =
|
||||||
|
"We're moving Native Integrations to Secret Syncs. Check the documentation at https://infisical.com/docs/integrations/secret-syncs/overview. If the integration you need isn't available in the Secret Syncs, please get in touch with us at [email protected].";
|
||||||
|
|
||||||
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -66,52 +68,58 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (_) => {
|
||||||
const { integration, integrationAuth } = await server.services.integration.createIntegration({
|
throw new ForbiddenRequestError({
|
||||||
actorId: req.permission.id,
|
message: NATIVE_INTEGRATION_DEPRECATION_MESSAGE
|
||||||
actor: req.permission.type,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
...req.body
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const createIntegrationEventProperty = shake({
|
// We are keeping the old response commented out for an easy revert on the API if we need to before the full phase out.
|
||||||
integrationId: integration.id.toString(),
|
|
||||||
integration: integration.integration,
|
|
||||||
environment: req.body.sourceEnvironment,
|
|
||||||
secretPath: req.body.secretPath,
|
|
||||||
url: integration.url,
|
|
||||||
app: integration.app,
|
|
||||||
appId: integration.appId,
|
|
||||||
targetEnvironment: integration.targetEnvironment,
|
|
||||||
targetEnvironmentId: integration.targetEnvironmentId,
|
|
||||||
targetService: integration.targetService,
|
|
||||||
targetServiceId: integration.targetServiceId,
|
|
||||||
path: integration.path,
|
|
||||||
region: integration.region
|
|
||||||
}) as TIntegrationCreatedEvent["properties"];
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
// const { integration, integrationAuth } = await server.services.integration.createIntegration({
|
||||||
...req.auditLogInfo,
|
// actorId: req.permission.id,
|
||||||
projectId: integrationAuth.projectId,
|
// actor: req.permission.type,
|
||||||
event: {
|
// actorAuthMethod: req.permission.authMethod,
|
||||||
type: EventType.CREATE_INTEGRATION,
|
// actorOrgId: req.permission.orgId,
|
||||||
// eslint-disable-next-line
|
// ...req.body
|
||||||
metadata: createIntegrationEventProperty
|
// });
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.telemetry.sendPostHogEvents({
|
// const createIntegrationEventProperty = shake({
|
||||||
event: PostHogEventTypes.IntegrationCreated,
|
// integrationId: integration.id.toString(),
|
||||||
organizationId: req.permission.orgId,
|
// integration: integration.integration,
|
||||||
distinctId: getTelemetryDistinctId(req),
|
// environment: req.body.sourceEnvironment,
|
||||||
properties: {
|
// secretPath: req.body.secretPath,
|
||||||
...createIntegrationEventProperty,
|
// url: integration.url,
|
||||||
projectId: integrationAuth.projectId,
|
// app: integration.app,
|
||||||
...req.auditLogInfo
|
// appId: integration.appId,
|
||||||
}
|
// targetEnvironment: integration.targetEnvironment,
|
||||||
});
|
// targetEnvironmentId: integration.targetEnvironmentId,
|
||||||
return { integration };
|
// targetService: integration.targetService,
|
||||||
|
// targetServiceId: integration.targetServiceId,
|
||||||
|
// path: integration.path,
|
||||||
|
// region: integration.region
|
||||||
|
// }) as TIntegrationCreatedEvent["properties"];
|
||||||
|
|
||||||
|
// await server.services.auditLog.createAuditLog({
|
||||||
|
// ...req.auditLogInfo,
|
||||||
|
// projectId: integrationAuth.projectId,
|
||||||
|
// event: {
|
||||||
|
// type: EventType.CREATE_INTEGRATION,
|
||||||
|
// // eslint-disable-next-line
|
||||||
|
// metadata: createIntegrationEventProperty
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// await server.services.telemetry.sendPostHogEvents({
|
||||||
|
// event: PostHogEventTypes.IntegrationCreated,
|
||||||
|
// organizationId: req.permission.orgId,
|
||||||
|
// distinctId: getTelemetryDistinctId(req),
|
||||||
|
// properties: {
|
||||||
|
// ...createIntegrationEventProperty,
|
||||||
|
// projectId: integrationAuth.projectId,
|
||||||
|
// ...req.auditLogInfo
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// return { integration };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Create Auth"
|
|
||||||
openapi: "POST /api/v1/integration-auth/access-token"
|
|
||||||
---
|
|
||||||
|
|
||||||
## Integration Authentication Parameters
|
|
||||||
|
|
||||||
The integration authentication endpoint is generic and can be used for all native integrations.
|
|
||||||
For specific integration parameters for a given service, please review the respective documentation below.
|
|
||||||
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="AWS Secrets manager">
|
|
||||||
<ParamField body="integration" type="string" initialValue="aws-secret-manager" required>
|
|
||||||
This value must be **aws-secret-manager**.
|
|
||||||
</ParamField>
|
|
||||||
<ParamField body="workspaceId" type="string" required>
|
|
||||||
Infisical project id for the integration.
|
|
||||||
</ParamField>
|
|
||||||
<ParamField body="accessId" type="string" required>
|
|
||||||
The AWS IAM User Access ID.
|
|
||||||
</ParamField>
|
|
||||||
<ParamField body="accessToken" type="string" required>
|
|
||||||
The AWS IAM User Access Secret Key.
|
|
||||||
</ParamField>
|
|
||||||
</Tab>
|
|
||||||
<Tab title="GCP Secrets manager">
|
|
||||||
Coming Soon
|
|
||||||
</Tab>
|
|
||||||
<Tab title="Heroku">
|
|
||||||
Coming Soon
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Create"
|
|
||||||
openapi: "POST /api/v1/integration"
|
|
||||||
---
|
|
||||||
|
|
||||||
## Integration Parameters
|
|
||||||
|
|
||||||
The integration creation endpoint is generic and can be used for all native integrations.
|
|
||||||
For specific integration parameters for a given service, please review the respective documentation below.
|
|
||||||
|
|
||||||
<Tabs>
|
|
||||||
<Tab title="AWS Secrets manager">
|
|
||||||
<ParamField body="integrationAuthId" type="string" required>
|
|
||||||
The ID of the integration auth object for authentication with AWS.
|
|
||||||
Refer [Create Integration Auth](./create-auth) for more info
|
|
||||||
</ParamField>
|
|
||||||
<ParamField body="isActive" type="boolean">
|
|
||||||
Whether the integration should be active or inactive
|
|
||||||
</ParamField>
|
|
||||||
<ParamField body="app" type="string" required>
|
|
||||||
The secret name used when saving secret in AWS SSM. Used for naming and can be arbitrary.
|
|
||||||
</ParamField>
|
|
||||||
<ParamField body="region" type="string" required>
|
|
||||||
The AWS region of the SSM. Example: `us-east-1`
|
|
||||||
</ParamField>
|
|
||||||
<ParamField body="sourceEnvironment" type="string" required>
|
|
||||||
The Infisical environment slug from where secrets will be synced from. Example: `dev`
|
|
||||||
</ParamField>
|
|
||||||
<ParamField body="secretPath" type="string" required>
|
|
||||||
The Infisical folder path from where secrets will be synced from. Example: `/some/path`. The root of the environment is `/`.
|
|
||||||
</ParamField>
|
|
||||||
</Tab>
|
|
||||||
<Tab title="GCP Secrets manager">
|
|
||||||
Coming Soon
|
|
||||||
</Tab>
|
|
||||||
<Tab title="Heroku">
|
|
||||||
Coming Soon
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Delete Auth By ID"
|
|
||||||
openapi: "DELETE /api/v1/integration-auth/{integrationAuthId}"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Delete Auth"
|
|
||||||
openapi: "DELETE /api/v1/integration-auth"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Delete"
|
|
||||||
openapi: "DELETE /api/v1/integration/{integrationId}"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Get Auth By ID"
|
|
||||||
openapi: "GET /api/v1/integration-auth/{integrationAuthId}"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "List Auth"
|
|
||||||
openapi: "GET /api/v1/workspace/{workspaceId}/authorizations"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "List Project Integrations"
|
|
||||||
openapi: "GET /api/v1/workspace/{workspaceId}/integrations"
|
|
||||||
---
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Update"
|
|
||||||
openapi: "PATCH /api/v1/integration/{integrationId}"
|
|
||||||
---
|
|
||||||
@@ -3,7 +3,15 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
import {
|
||||||
|
Alert,
|
||||||
|
AlertDescription,
|
||||||
|
PageHeader,
|
||||||
|
Tab,
|
||||||
|
TabList,
|
||||||
|
TabPanel,
|
||||||
|
Tabs
|
||||||
|
} from "@app/components/v2";
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -12,6 +20,7 @@ import {
|
|||||||
useProject
|
useProject
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
import { useGetWorkspaceIntegrations } from "@app/hooks/api";
|
||||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||||
|
|
||||||
@@ -32,6 +41,9 @@ export const IntegrationsListPage = () => {
|
|||||||
from: ROUTE_PATHS.SecretManager.IntegrationsListPage.id
|
from: ROUTE_PATHS.SecretManager.IntegrationsListPage.id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: integrations } = useGetWorkspaceIntegrations(currentProject.id);
|
||||||
|
const hasNativeIntegrations = Boolean(integrations?.length);
|
||||||
|
|
||||||
const updateSelectedTab = (tab: string) => {
|
const updateSelectedTab = (tab: string) => {
|
||||||
navigate({
|
navigate({
|
||||||
to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path,
|
to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path,
|
||||||
@@ -65,9 +77,11 @@ export const IntegrationsListPage = () => {
|
|||||||
<Tab variant="project" value={IntegrationsListPageTabs.SecretSyncs}>
|
<Tab variant="project" value={IntegrationsListPageTabs.SecretSyncs}>
|
||||||
Secret Syncs
|
Secret Syncs
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab variant="project" value={IntegrationsListPageTabs.NativeIntegrations}>
|
{hasNativeIntegrations && (
|
||||||
Native Integrations
|
<Tab variant="project" value={IntegrationsListPageTabs.NativeIntegrations}>
|
||||||
</Tab>
|
Native Integrations
|
||||||
|
</Tab>
|
||||||
|
)}
|
||||||
<Tab variant="project" value={IntegrationsListPageTabs.FrameworkIntegrations}>
|
<Tab variant="project" value={IntegrationsListPageTabs.FrameworkIntegrations}>
|
||||||
Framework Integrations
|
Framework Integrations
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -84,15 +98,39 @@ export const IntegrationsListPage = () => {
|
|||||||
<SecretSyncsTab />
|
<SecretSyncsTab />
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={IntegrationsListPageTabs.NativeIntegrations}>
|
{hasNativeIntegrations && (
|
||||||
<ProjectPermissionCan
|
<TabPanel value={IntegrationsListPageTabs.NativeIntegrations}>
|
||||||
renderGuardBanner
|
<Alert variant="warning" className="mb-4" hideTitle>
|
||||||
I={ProjectPermissionActions.Read}
|
<AlertDescription>
|
||||||
a={ProjectPermissionSub.Integrations}
|
We're moving Native Integrations to{" "}
|
||||||
>
|
<a
|
||||||
<NativeIntegrationsTab />
|
href="https://infisical.com/docs/integrations/secret-syncs/overview"
|
||||||
</ProjectPermissionCan>
|
target="_blank"
|
||||||
</TabPanel>
|
rel="noopener noreferrer"
|
||||||
|
className="underline underline-offset-2 hover:text-mineshaft-100"
|
||||||
|
>
|
||||||
|
Secret Syncs
|
||||||
|
</a>
|
||||||
|
. If the integration you need isn't available in the Secret Syncs menu,
|
||||||
|
please get in touch with us at{" "}
|
||||||
|
<a
|
||||||
|
href="mailto:[email protected]"
|
||||||
|
className="underline underline-offset-2 hover:text-mineshaft-100"
|
||||||
|
>
|
||||||
|
team@infisical.com
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
renderGuardBanner
|
||||||
|
I={ProjectPermissionActions.Read}
|
||||||
|
a={ProjectPermissionSub.Integrations}
|
||||||
|
>
|
||||||
|
<NativeIntegrationsTab />
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</TabPanel>
|
||||||
|
)}
|
||||||
<TabPanel value={IntegrationsListPageTabs.FrameworkIntegrations}>
|
<TabPanel value={IntegrationsListPageTabs.FrameworkIntegrations}>
|
||||||
<FrameworkIntegrationTab />
|
<FrameworkIntegrationTab />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|||||||
-352
@@ -1,10 +1,4 @@
|
|||||||
import crypto from "crypto";
|
|
||||||
|
|
||||||
import { NavigateFn } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { localStorageService } from "@app/helpers/localStorage";
|
|
||||||
import { TCloudIntegration } from "@app/hooks/api/types";
|
|
||||||
|
|
||||||
export const createIntegrationMissingEnvVarsNotification = (
|
export const createIntegrationMissingEnvVarsNotification = (
|
||||||
slug: string,
|
slug: string,
|
||||||
@@ -27,349 +21,3 @@ export const createIntegrationMissingEnvVarsNotification = (
|
|||||||
),
|
),
|
||||||
title: "Missing Environment Variables"
|
title: "Missing Environment Variables"
|
||||||
});
|
});
|
||||||
|
|
||||||
export const redirectForProviderAuth = (
|
|
||||||
orgId: string,
|
|
||||||
projectId: string,
|
|
||||||
navigate: NavigateFn,
|
|
||||||
integrationOption: TCloudIntegration
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
// generate CSRF token for OAuth2 code-token exchange integrations
|
|
||||||
const state = crypto.randomBytes(16).toString("hex");
|
|
||||||
localStorage.setItem("latestCSRFToken", state);
|
|
||||||
localStorageService.setIntegrationProjectId(projectId);
|
|
||||||
|
|
||||||
switch (integrationOption.slug) {
|
|
||||||
case "gcp-secret-manager":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/gcp-secret-manager/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "azure-key-vault": {
|
|
||||||
if (!integrationOption.clientId) {
|
|
||||||
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/azure-key-vault/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
clientId: integrationOption.clientId,
|
|
||||||
state
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "azure-app-configuration": {
|
|
||||||
if (!integrationOption.clientId) {
|
|
||||||
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const link = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/azure-app-configuration/oauth2/callback&response_mode=query&scope=https://azconfig.io/.default openid offline_access&state=${state}`;
|
|
||||||
window.location.assign(link);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "aws-parameter-store":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/aws-parameter-store/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "aws-secret-manager":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/aws-secret-manager/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "heroku": {
|
|
||||||
if (!integrationOption.clientId) {
|
|
||||||
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const link = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`;
|
|
||||||
window.location.assign(link);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "vercel": {
|
|
||||||
if (!integrationOption.clientSlug) {
|
|
||||||
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const link = `https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`;
|
|
||||||
window.location.assign(link);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "netlify": {
|
|
||||||
if (!integrationOption.clientId) {
|
|
||||||
createIntegrationMissingEnvVarsNotification(integrationOption.slug);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const link = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/integrations/netlify/oauth2/callback`;
|
|
||||||
|
|
||||||
window.location.assign(link);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "github":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/github/auth-mode-selection",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "gitlab":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/gitlab/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "render":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/render/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "flyio":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/flyio/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "circleci":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/circleci/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "databricks":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/databricks/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "laravel-forge":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/laravel-forge/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "travisci":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/travisci/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "supabase":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/supabase/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "checkly":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/checkly/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "qovery":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/qovery/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "railway":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/railway/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "terraform-cloud":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/terraform-cloud/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "hashicorp-vault":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/hashicorp-vault/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "cloudflare-pages":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/cloudflare-pages/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "cloudflare-workers":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/cloudflare-workers/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "bitbucket": {
|
|
||||||
if (!integrationOption.clientId) {
|
|
||||||
createIntegrationMissingEnvVarsNotification(integrationOption.slug, "cicd");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const link = `https://bitbucket.org/site/oauth2/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/bitbucket/oauth2/callback&state=${state}`;
|
|
||||||
window.location.assign(link);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "codefresh":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/codefresh/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "digital-ocean-app-platform":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/digital-ocean-app-platform/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "cloud-66":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/cloud-66/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "northflank":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/northflank/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "windmill":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/windmill/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "teamcity":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/teamcity/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "hasura-cloud":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/hasura-cloud/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "rundeck":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/rundeck/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "azure-devops":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/azure-devops/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "octopus-deploy":
|
|
||||||
navigate({
|
|
||||||
to: "/organizations/$orgId/projects/secret-management/$projectId/integrations/octopus-deploy/authorize",
|
|
||||||
params: {
|
|
||||||
orgId,
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
-258
@@ -1,258 +0,0 @@
|
|||||||
import { useMemo, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import {
|
|
||||||
faCheck,
|
|
||||||
faChevronLeft,
|
|
||||||
faMagnifyingGlass,
|
|
||||||
faSearch,
|
|
||||||
faXmark
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
import { NoEnvironmentsBanner } from "@app/components/integrations/NoEnvironmentsBanner";
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
DeleteActionModal,
|
|
||||||
EmptyState,
|
|
||||||
Input,
|
|
||||||
Skeleton,
|
|
||||||
Tooltip
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
|
||||||
import {
|
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSub,
|
|
||||||
useOrganization,
|
|
||||||
useProject,
|
|
||||||
useProjectPermission
|
|
||||||
} from "@app/context";
|
|
||||||
import { usePopUp } from "@app/hooks";
|
|
||||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
|
||||||
import { IntegrationAuth, TCloudIntegration } from "@app/hooks/api/types";
|
|
||||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isLoading?: boolean;
|
|
||||||
integrationAuths?: Record<string, IntegrationAuth>;
|
|
||||||
cloudIntegrations?: TCloudIntegration[];
|
|
||||||
onIntegrationStart: (slug: string) => void;
|
|
||||||
// cb: handle popUpClose child->parent communication pattern
|
|
||||||
onIntegrationRevoke: (slug: string, cb: () => void) => void;
|
|
||||||
onViewActiveIntegrations?: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TRevokeIntegrationPopUp = { provider: string };
|
|
||||||
|
|
||||||
const SECRET_SYNCS = Object.values(SecretSync) as string[];
|
|
||||||
const isSecretSyncAvailable = (type: string) => SECRET_SYNCS.includes(type);
|
|
||||||
|
|
||||||
export const CloudIntegrationSection = ({
|
|
||||||
isLoading,
|
|
||||||
cloudIntegrations = [],
|
|
||||||
integrationAuths = {},
|
|
||||||
onIntegrationStart,
|
|
||||||
onIntegrationRevoke,
|
|
||||||
onViewActiveIntegrations
|
|
||||||
}: Props) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
|
||||||
"deleteConfirmation"
|
|
||||||
] as const);
|
|
||||||
const { permission } = useProjectPermission();
|
|
||||||
const { currentOrg } = useOrganization();
|
|
||||||
const { currentProject } = useProject();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const isEmpty = !isLoading && !cloudIntegrations?.length;
|
|
||||||
|
|
||||||
const sortedCloudIntegrations = useMemo(() => {
|
|
||||||
const sortedIntegrations = cloudIntegrations.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
|
|
||||||
if (currentProject?.environments.length === 0) {
|
|
||||||
return sortedIntegrations.map((integration) => ({ ...integration, isAvailable: false }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortedIntegrations;
|
|
||||||
}, [cloudIntegrations, currentProject?.environments]);
|
|
||||||
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
const filteredIntegrations = sortedCloudIntegrations?.filter((cloudIntegration) =>
|
|
||||||
cloudIntegration.name.toLowerCase().includes(search.toLowerCase().trim())
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{currentProject?.environments.length === 0 && (
|
|
||||||
<div className="px-5">
|
|
||||||
<NoEnvironmentsBanner projectId={currentProject.id} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="m-4 mt-0 flex flex-col items-start justify-between px-2 text-xl">
|
|
||||||
{onViewActiveIntegrations && (
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
onClick={onViewActiveIntegrations}
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
|
||||||
>
|
|
||||||
Back to Integrations
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<div className="flex w-full flex-col justify-between gap-4 whitespace-nowrap lg:flex-row lg:items-end lg:gap-8">
|
|
||||||
<div className="flex-1">
|
|
||||||
<h1 className="text-3xl font-medium">{t("integrations.cloud-integrations")}</h1>
|
|
||||||
<p className="text-base text-gray-400">{t("integrations.click-to-start")}</p>
|
|
||||||
</div>
|
|
||||||
<Input
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
|
||||||
placeholder="Search cloud integrations..."
|
|
||||||
containerClassName="flex-1 h-min text-base"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx-2 grid grid-cols-3 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7">
|
|
||||||
{isLoading &&
|
|
||||||
Array.from({ length: 12 }).map((_, index) => (
|
|
||||||
<Skeleton className="h-32" key={`cloud-integration-skeleton-${index + 1}`} />
|
|
||||||
))}
|
|
||||||
|
|
||||||
{!isLoading && filteredIntegrations.length ? (
|
|
||||||
filteredIntegrations.map((cloudIntegration) => {
|
|
||||||
const syncSlug = cloudIntegration.syncSlug ?? cloudIntegration.slug;
|
|
||||||
const isSyncAvailable = isSecretSyncAvailable(syncSlug);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
onKeyDown={() => null}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
className={`group relative ${
|
|
||||||
cloudIntegration.isAvailable
|
|
||||||
? "cursor-pointer duration-200 hover:bg-mineshaft-700"
|
|
||||||
: "opacity-50"
|
|
||||||
} flex h-36 flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3`}
|
|
||||||
onClick={() => {
|
|
||||||
if (isSyncAvailable) {
|
|
||||||
navigate({
|
|
||||||
to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path,
|
|
||||||
params: {
|
|
||||||
orgId: currentOrg.id,
|
|
||||||
projectId: currentProject.id
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
selectedTab: IntegrationsListPageTabs.SecretSyncs,
|
|
||||||
addSync: syncSlug as SecretSync
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!cloudIntegration.isAvailable) return;
|
|
||||||
if (
|
|
||||||
permission.cannot(
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionSub.Integrations
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "You do not have permission to create an integration"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onIntegrationStart(cloudIntegration.slug);
|
|
||||||
}}
|
|
||||||
key={cloudIntegration.slug}
|
|
||||||
>
|
|
||||||
<div className="m-auto flex flex-col items-center">
|
|
||||||
<img
|
|
||||||
src={`/images/integrations/${cloudIntegration.image}`}
|
|
||||||
height={60}
|
|
||||||
width={60}
|
|
||||||
className="mt-auto"
|
|
||||||
alt="integration logo"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={`mt-2 max-w-xs text-center text-sm font-medium text-gray-300 duration-200 group-hover:text-gray-200 ${isSyncAvailable ? "mb-4" : ""}`}
|
|
||||||
>
|
|
||||||
{cloudIntegration.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{cloudIntegration.isAvailable &&
|
|
||||||
Boolean(integrationAuths?.[cloudIntegration.slug]) && (
|
|
||||||
<div className="absolute top-0 right-0 z-30 h-full">
|
|
||||||
<div className="relative h-full">
|
|
||||||
<div className="absolute top-0 right-0 w-24 flex-row items-center overflow-hidden rounded-tr-md rounded-bl-md bg-primary px-2 py-0.5 text-xs whitespace-nowrap text-black opacity-80 transition-all duration-300 group-hover:w-0 group-hover:p-0">
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="mr-2 text-xs" />
|
|
||||||
Authorized
|
|
||||||
</div>
|
|
||||||
<Tooltip content="Revoke Access">
|
|
||||||
<div
|
|
||||||
onKeyDown={() => null}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
onClick={async (event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
handlePopUpOpen("deleteConfirmation", {
|
|
||||||
provider: cloudIntegration.slug
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="absolute top-0 right-0 flex h-0 w-12 cursor-pointer items-center justify-center overflow-hidden rounded-r-md bg-red text-xs opacity-50 transition-all duration-300 group-hover:h-full hover:opacity-100"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faXmark} size="xl" />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isSyncAvailable && (
|
|
||||||
<div className="absolute bottom-0 left-0 z-30 h-full w-full">
|
|
||||||
<div className="relative h-full">
|
|
||||||
<div className="absolute bottom-0 left-0 w-full flex-row overflow-hidden rounded-br-md rounded-bl-md bg-yellow/20 px-2 py-0.5 text-center text-xs whitespace-nowrap text-yellow">
|
|
||||||
Secret Sync Available
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<EmptyState
|
|
||||||
className="col-span-full h-32 w-full rounded-md bg-transparent pt-14"
|
|
||||||
title="No cloud integrations match search..."
|
|
||||||
icon={faSearch}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isEmpty && (
|
|
||||||
<div className="mx-6 grid max-w-5xl grid-cols-4 grid-rows-2 gap-4">
|
|
||||||
{Array.from({ length: 16 }).map((_, index) => (
|
|
||||||
<div
|
|
||||||
key={`dummy-cloud-integration-${index + 1}`}
|
|
||||||
className="h-32 animate-pulse rounded-md border border-mineshaft-600 bg-mineshaft-800"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<DeleteActionModal
|
|
||||||
isOpen={popUp.deleteConfirmation.isOpen}
|
|
||||||
title={`Are you sure you want to revoke access ${
|
|
||||||
(popUp?.deleteConfirmation.data as TRevokeIntegrationPopUp)?.provider || " "
|
|
||||||
}?`}
|
|
||||||
subTitle="This will remove all the secret integration of this provider!!!"
|
|
||||||
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
|
|
||||||
deleteKey={(popUp?.deleteConfirmation?.data as TRevokeIntegrationPopUp)?.provider || ""}
|
|
||||||
onDeleteApproved={async () => {
|
|
||||||
onIntegrationRevoke(
|
|
||||||
(popUp.deleteConfirmation.data as TRevokeIntegrationPopUp)?.provider,
|
|
||||||
() => handlePopUpClose("deleteConfirmation")
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
export { CloudIntegrationSection } from "./CloudIntegrationSection";
|
|
||||||
+17
-88
@@ -1,11 +1,8 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, Checkbox, DeleteActionModal, Spinner } from "@app/components/v2";
|
import { Checkbox, DeleteActionModal, Spinner } from "@app/components/v2";
|
||||||
import { useOrganization, useProject } from "@app/context";
|
import { useProject } from "@app/context";
|
||||||
import { usePopUp, useToggle } from "@app/hooks";
|
import { usePopUp, useToggle } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useDeleteIntegration,
|
useDeleteIntegration,
|
||||||
@@ -17,38 +14,26 @@ import {
|
|||||||
import { IntegrationAuth } from "@app/hooks/api/integrationAuth/types";
|
import { IntegrationAuth } from "@app/hooks/api/integrationAuth/types";
|
||||||
import { TIntegration } from "@app/hooks/api/integrations/types";
|
import { TIntegration } from "@app/hooks/api/integrations/types";
|
||||||
|
|
||||||
import { redirectForProviderAuth } from "../../IntegrationsListPage.utils";
|
|
||||||
import { CloudIntegrationSection } from "../CloudIntegrationSection";
|
|
||||||
import { IntegrationsTable } from "./IntegrationsTable";
|
import { IntegrationsTable } from "./IntegrationsTable";
|
||||||
|
|
||||||
enum IntegrationView {
|
|
||||||
List = "list",
|
|
||||||
New = "new"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NativeIntegrationsTab = () => {
|
export const NativeIntegrationsTab = () => {
|
||||||
const { currentOrg } = useOrganization();
|
|
||||||
const { currentProject } = useProject();
|
const { currentProject } = useProject();
|
||||||
const { environments, id: workspaceId } = currentProject;
|
const { environments, id: workspaceId } = currentProject;
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const { data: cloudIntegrations, isPending: isCloudIntegrationsLoading } =
|
const { data: cloudIntegrations, isPending: isCloudIntegrationsLoading } =
|
||||||
useGetCloudIntegrations();
|
useGetCloudIntegrations();
|
||||||
|
|
||||||
const {
|
const { data: integrationAuths, isFetching: isIntegrationAuthFetching } =
|
||||||
data: integrationAuths,
|
useGetWorkspaceAuthorizations(
|
||||||
isPending: isIntegrationAuthLoading,
|
workspaceId,
|
||||||
isFetching: isIntegrationAuthFetching
|
useCallback((data: IntegrationAuth[]) => {
|
||||||
} = useGetWorkspaceAuthorizations(
|
const groupBy: Record<string, IntegrationAuth> = {};
|
||||||
workspaceId,
|
data.forEach((el) => {
|
||||||
useCallback((data: IntegrationAuth[]) => {
|
groupBy[el.integration] = el;
|
||||||
const groupBy: Record<string, IntegrationAuth> = {};
|
});
|
||||||
data.forEach((el) => {
|
return groupBy;
|
||||||
groupBy[el.integration] = el;
|
}, [])
|
||||||
});
|
);
|
||||||
return groupBy;
|
|
||||||
}, [])
|
|
||||||
);
|
|
||||||
|
|
||||||
// mutation
|
// mutation
|
||||||
const {
|
const {
|
||||||
@@ -58,11 +43,8 @@ export const NativeIntegrationsTab = () => {
|
|||||||
} = useGetWorkspaceIntegrations(workspaceId);
|
} = useGetWorkspaceIntegrations(workspaceId);
|
||||||
|
|
||||||
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
|
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
|
||||||
const {
|
|
||||||
mutateAsync: deleteIntegrationAuths,
|
const { reset: resetDeleteIntegrationAuths } = useDeleteIntegrationAuths();
|
||||||
isSuccess: isDeleteIntegrationAuthSuccess,
|
|
||||||
reset: resetDeleteIntegrationAuths
|
|
||||||
} = useDeleteIntegrationAuths();
|
|
||||||
|
|
||||||
const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).length;
|
const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).length;
|
||||||
const isIntegrationsEmpty = !integrations?.length;
|
const isIntegrationsEmpty = !integrations?.length;
|
||||||
@@ -71,7 +53,6 @@ export const NativeIntegrationsTab = () => {
|
|||||||
// After the refetch is completed check if its empty. Then set bot active and reset the submit hook for isSuccess to go back to false
|
// After the refetch is completed check if its empty. Then set bot active and reset the submit hook for isSuccess to go back to false
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
isDeleteIntegrationAuthSuccess &&
|
|
||||||
!isIntegrationFetching &&
|
!isIntegrationFetching &&
|
||||||
!isIntegrationAuthFetching &&
|
!isIntegrationAuthFetching &&
|
||||||
isIntegrationsAuthorizedEmpty &&
|
isIntegrationsAuthorizedEmpty &&
|
||||||
@@ -81,29 +62,11 @@ export const NativeIntegrationsTab = () => {
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isIntegrationFetching,
|
isIntegrationFetching,
|
||||||
isDeleteIntegrationAuthSuccess,
|
|
||||||
isIntegrationAuthFetching,
|
isIntegrationAuthFetching,
|
||||||
isIntegrationsAuthorizedEmpty,
|
isIntegrationsAuthorizedEmpty,
|
||||||
isIntegrationsEmpty
|
isIntegrationsEmpty
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleProviderIntegration = async (provider: string) => {
|
|
||||||
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
|
|
||||||
if (!selectedCloudIntegration) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
redirectForProviderAuth(currentOrg.id, currentProject.id, navigate, selectedCloudIntegration);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// function to strat integration for a provider
|
|
||||||
// confirmation to user passing the bot key for provider to get secret access
|
|
||||||
const handleProviderIntegrationStart = (provider: string) => {
|
|
||||||
handleProviderIntegration(provider);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleIntegrationDelete = async (
|
const handleIntegrationDelete = async (
|
||||||
integrationId: string,
|
integrationId: string,
|
||||||
shouldDeleteIntegrationSecrets: boolean,
|
shouldDeleteIntegrationSecrets: boolean,
|
||||||
@@ -117,28 +80,11 @@ export const NativeIntegrationsTab = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => {
|
|
||||||
const integrationAuthForProvider = integrationAuths?.[provider];
|
|
||||||
if (!integrationAuthForProvider) return;
|
|
||||||
|
|
||||||
await deleteIntegrationAuths({
|
|
||||||
integration: provider,
|
|
||||||
workspaceId
|
|
||||||
});
|
|
||||||
if (cb) cb();
|
|
||||||
createNotification({
|
|
||||||
type: "success",
|
|
||||||
text: "Revoked provider authentication"
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"deleteConfirmation",
|
"deleteConfirmation",
|
||||||
"deleteSecretsConfirmation"
|
"deleteSecretsConfirmation"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const [view, setView] = useState<IntegrationView>(IntegrationView.List);
|
|
||||||
|
|
||||||
const [shouldDeleteSecrets, setShouldDeleteSecrets] = useToggle(false);
|
const [shouldDeleteSecrets, setShouldDeleteSecrets] = useToggle(false);
|
||||||
|
|
||||||
if (isIntegrationLoading || isCloudIntegrationsLoading)
|
if (isIntegrationLoading || isCloudIntegrationsLoading)
|
||||||
@@ -150,18 +96,10 @@ export const NativeIntegrationsTab = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{view === IntegrationView.List ? (
|
{integrations?.length && (
|
||||||
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<p className="text-xl font-medium text-mineshaft-100">Native Integrations</p>
|
<p className="text-xl font-medium text-mineshaft-100">Native Integrations</p>
|
||||||
<Button
|
|
||||||
colorSchema="secondary"
|
|
||||||
type="submit"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
||||||
onClick={() => setView(IntegrationView.New)}
|
|
||||||
>
|
|
||||||
Add Integration
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<IntegrationsTable
|
<IntegrationsTable
|
||||||
cloudIntegrations={cloudIntegrations}
|
cloudIntegrations={cloudIntegrations}
|
||||||
@@ -175,15 +113,6 @@ export const NativeIntegrationsTab = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<CloudIntegrationSection
|
|
||||||
onIntegrationStart={handleProviderIntegrationStart}
|
|
||||||
onIntegrationRevoke={handleIntegrationAuthRevoke}
|
|
||||||
integrationAuths={integrationAuths}
|
|
||||||
cloudIntegrations={cloudIntegrations}
|
|
||||||
isLoading={isIntegrationAuthLoading || isCloudIntegrationsLoading}
|
|
||||||
onViewActiveIntegrations={() => setView(IntegrationView.List)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.deleteConfirmation.isOpen}
|
isOpen={popUp.deleteConfirmation.isOpen}
|
||||||
|
|||||||
Reference in New Issue
Block a user