diff --git a/backend/src/server/routes/v1/integration-auth-router.ts b/backend/src/server/routes/v1/integration-auth-router.ts index e5156724d..40bb8fa13 100644 --- a/backend/src/server/routes/v1/integration-auth-router.ts +++ b/backend/src/server/routes/v1/integration-auth-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ApiDocsTags, INTEGRATION_AUTH } from "@app/lib/api-docs"; +import { ForbiddenRequestError } from "@app/lib/errors"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; 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"; +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 team@infisical.com."; + export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", @@ -333,27 +337,33 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) }) } }, - handler: async (req) => { - const integrationAuth = await server.services.integrationAuth.saveIntegrationToken({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.body.workspaceId, - ...req.body + handler: async (_) => { + throw new ForbiddenRequestError({ + message: NATIVE_INTEGRATION_DEPRECATION_MESSAGE }); - await server.services.auditLog.createAuditLog({ - ...req.auditLogInfo, - projectId: req.body.workspaceId, - event: { - type: EventType.AUTHORIZE_INTEGRATION, - metadata: { - integration: integrationAuth.integration - } - } - }); - return { integrationAuth }; + // We are keeping the old response commented out for an easy revert on the API if we need to before the full phase out. + + // const integrationAuth = await server.services.integrationAuth.saveIntegrationToken({ + // actorId: req.permission.id, + // actor: req.permission.type, + // actorAuthMethod: req.permission.authMethod, + // actorOrgId: req.permission.orgId, + // projectId: req.body.workspaceId, + // ...req.body + // }); + + // await server.services.auditLog.createAuditLog({ + // ...req.auditLogInfo, + // projectId: req.body.workspaceId, + // event: { + // type: EventType.AUTHORIZE_INTEGRATION, + // metadata: { + // integration: integrationAuth.integration + // } + // } + // }); + // return { integrationAuth }; } }); diff --git a/backend/src/server/routes/v1/integration-router.ts b/backend/src/server/routes/v1/integration-router.ts index 95477c341..183c5a38b 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -3,17 +3,19 @@ import { z } from "zod"; import { IntegrationsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ApiDocsTags, INTEGRATION } from "@app/lib/api-docs"; +import { ForbiddenRequestError } from "@app/lib/errors"; import { removeTrailingSlash, shake } from "@app/lib/fn"; 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 { AuthMode } from "@app/services/auth/auth-type"; import { IntegrationMetadataSchema } from "@app/services/integration/integration-schema"; import { Integrations } from "@app/services/integration-auth/integration-list"; -import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types"; 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 team@infisical.com."; + export const registerIntegrationRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", @@ -66,52 +68,58 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: async (req) => { - const { integration, integrationAuth } = await server.services.integration.createIntegration({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - ...req.body + handler: async (_) => { + throw new ForbiddenRequestError({ + message: NATIVE_INTEGRATION_DEPRECATION_MESSAGE }); - const createIntegrationEventProperty = shake({ - 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"]; + // We are keeping the old response commented out for an easy revert on the API if we need to before the full phase out. - await server.services.auditLog.createAuditLog({ - ...req.auditLogInfo, - projectId: integrationAuth.projectId, - event: { - type: EventType.CREATE_INTEGRATION, - // eslint-disable-next-line - metadata: createIntegrationEventProperty - } - }); + // const { integration, integrationAuth } = await server.services.integration.createIntegration({ + // actorId: req.permission.id, + // actor: req.permission.type, + // actorAuthMethod: req.permission.authMethod, + // actorOrgId: req.permission.orgId, + // ...req.body + // }); - await server.services.telemetry.sendPostHogEvents({ - event: PostHogEventTypes.IntegrationCreated, - organizationId: req.permission.orgId, - distinctId: getTelemetryDistinctId(req), - properties: { - ...createIntegrationEventProperty, - projectId: integrationAuth.projectId, - ...req.auditLogInfo - } - }); - return { integration }; + // const createIntegrationEventProperty = shake({ + // 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({ + // ...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 }; } }); diff --git a/docs/api-reference/endpoints/integrations/create-auth.mdx b/docs/api-reference/endpoints/integrations/create-auth.mdx deleted file mode 100644 index 5af7a0f9c..000000000 --- a/docs/api-reference/endpoints/integrations/create-auth.mdx +++ /dev/null @@ -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. - - - - - This value must be **aws-secret-manager**. - - - Infisical project id for the integration. - - - The AWS IAM User Access ID. - - - The AWS IAM User Access Secret Key. - - - - Coming Soon - - - Coming Soon - - diff --git a/docs/api-reference/endpoints/integrations/create.mdx b/docs/api-reference/endpoints/integrations/create.mdx deleted file mode 100644 index 0992e91b9..000000000 --- a/docs/api-reference/endpoints/integrations/create.mdx +++ /dev/null @@ -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. - - - - - The ID of the integration auth object for authentication with AWS. - Refer [Create Integration Auth](./create-auth) for more info - - - Whether the integration should be active or inactive - - - The secret name used when saving secret in AWS SSM. Used for naming and can be arbitrary. - - - The AWS region of the SSM. Example: `us-east-1` - - - The Infisical environment slug from where secrets will be synced from. Example: `dev` - - - The Infisical folder path from where secrets will be synced from. Example: `/some/path`. The root of the environment is `/`. - - - - Coming Soon - - - Coming Soon - - - diff --git a/docs/api-reference/endpoints/integrations/delete-auth-by-id.mdx b/docs/api-reference/endpoints/integrations/delete-auth-by-id.mdx deleted file mode 100644 index 5884363fc..000000000 --- a/docs/api-reference/endpoints/integrations/delete-auth-by-id.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Delete Auth By ID" -openapi: "DELETE /api/v1/integration-auth/{integrationAuthId}" ---- diff --git a/docs/api-reference/endpoints/integrations/delete-auth.mdx b/docs/api-reference/endpoints/integrations/delete-auth.mdx deleted file mode 100644 index 93d957903..000000000 --- a/docs/api-reference/endpoints/integrations/delete-auth.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Delete Auth" -openapi: "DELETE /api/v1/integration-auth" ---- diff --git a/docs/api-reference/endpoints/integrations/delete.mdx b/docs/api-reference/endpoints/integrations/delete.mdx deleted file mode 100644 index 51df56de7..000000000 --- a/docs/api-reference/endpoints/integrations/delete.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Delete" -openapi: "DELETE /api/v1/integration/{integrationId}" ---- diff --git a/docs/api-reference/endpoints/integrations/find-auth.mdx b/docs/api-reference/endpoints/integrations/find-auth.mdx deleted file mode 100644 index 439b82935..000000000 --- a/docs/api-reference/endpoints/integrations/find-auth.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Get Auth By ID" -openapi: "GET /api/v1/integration-auth/{integrationAuthId}" ---- diff --git a/docs/api-reference/endpoints/integrations/list-auth.mdx b/docs/api-reference/endpoints/integrations/list-auth.mdx deleted file mode 100644 index 3ca961d98..000000000 --- a/docs/api-reference/endpoints/integrations/list-auth.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "List Auth" -openapi: "GET /api/v1/workspace/{workspaceId}/authorizations" ---- diff --git a/docs/api-reference/endpoints/integrations/list-project-integrations.mdx b/docs/api-reference/endpoints/integrations/list-project-integrations.mdx deleted file mode 100644 index 24ebbf7d8..000000000 --- a/docs/api-reference/endpoints/integrations/list-project-integrations.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "List Project Integrations" -openapi: "GET /api/v1/workspace/{workspaceId}/integrations" ---- diff --git a/docs/api-reference/endpoints/integrations/update.mdx b/docs/api-reference/endpoints/integrations/update.mdx deleted file mode 100644 index 8567c46ae..000000000 --- a/docs/api-reference/endpoints/integrations/update.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Update" -openapi: "PATCH /api/v1/integration/{integrationId}" ---- diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx index bd6ff70d1..4b2d5f750 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx @@ -3,7 +3,15 @@ import { useTranslation } from "react-i18next"; import { useNavigate, useSearch } from "@tanstack/react-router"; 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 { ProjectPermissionActions, @@ -12,6 +20,7 @@ import { useProject } from "@app/context"; import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types"; +import { useGetWorkspaceIntegrations } from "@app/hooks/api"; import { ProjectType } from "@app/hooks/api/projects/types"; import { IntegrationsListPageTabs } from "@app/types/integrations"; @@ -32,6 +41,9 @@ export const IntegrationsListPage = () => { from: ROUTE_PATHS.SecretManager.IntegrationsListPage.id }); + const { data: integrations } = useGetWorkspaceIntegrations(currentProject.id); + const hasNativeIntegrations = Boolean(integrations?.length); + const updateSelectedTab = (tab: string) => { navigate({ to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path, @@ -65,9 +77,11 @@ export const IntegrationsListPage = () => { Secret Syncs - - Native Integrations - + {hasNativeIntegrations && ( + + Native Integrations + + )} Framework Integrations @@ -84,15 +98,39 @@ export const IntegrationsListPage = () => { - - - - - + {hasNativeIntegrations && ( + + + + We're moving Native Integrations to{" "} + + Secret Syncs + + . If the integration you need isn't available in the Secret Syncs menu, + please get in touch with us at{" "} + + team@infisical.com + + . + + + + + + + )} diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx index 9332023f5..bb9d02d3d 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx @@ -1,10 +1,4 @@ -import crypto from "crypto"; - -import { NavigateFn } from "@tanstack/react-router"; - import { createNotification } from "@app/components/notifications"; -import { localStorageService } from "@app/helpers/localStorage"; -import { TCloudIntegration } from "@app/hooks/api/types"; export const createIntegrationMissingEnvVarsNotification = ( slug: string, @@ -27,349 +21,3 @@ export const createIntegrationMissingEnvVarsNotification = ( ), 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); - } -}; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx deleted file mode 100644 index ff92f9f41..000000000 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx +++ /dev/null @@ -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; - 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 ( -
- {currentProject?.environments.length === 0 && ( -
- -
- )} -
- {onViewActiveIntegrations && ( - - )} -
-
-

{t("integrations.cloud-integrations")}

-

{t("integrations.click-to-start")}

-
- setSearch(e.target.value)} - leftIcon={} - placeholder="Search cloud integrations..." - containerClassName="flex-1 h-min text-base" - /> -
-
-
- {isLoading && - Array.from({ length: 12 }).map((_, index) => ( - - ))} - - {!isLoading && filteredIntegrations.length ? ( - filteredIntegrations.map((cloudIntegration) => { - const syncSlug = cloudIntegration.syncSlug ?? cloudIntegration.slug; - const isSyncAvailable = isSecretSyncAvailable(syncSlug); - - return ( -
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} - > -
- integration logo -
- {cloudIntegration.name} -
-
- {cloudIntegration.isAvailable && - Boolean(integrationAuths?.[cloudIntegration.slug]) && ( -
-
-
- - Authorized -
- -
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" - > - -
-
-
-
- )} - {isSyncAvailable && ( -
-
-
- Secret Sync Available -
-
-
- )} -
- ); - }) - ) : ( - - )} -
- {isEmpty && ( -
- {Array.from({ length: 16 }).map((_, index) => ( -
- ))} -
- )} - handlePopUpToggle("deleteConfirmation", isOpen)} - deleteKey={(popUp?.deleteConfirmation?.data as TRevokeIntegrationPopUp)?.provider || ""} - onDeleteApproved={async () => { - onIntegrationRevoke( - (popUp.deleteConfirmation.data as TRevokeIntegrationPopUp)?.provider, - () => handlePopUpClose("deleteConfirmation") - ); - }} - /> -
- ); -}; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/index.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/index.tsx deleted file mode 100644 index 62f7a006c..000000000 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/CloudIntegrationSection/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { CloudIntegrationSection } from "./CloudIntegrationSection"; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx index 967027857..b51c06df4 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx @@ -1,11 +1,8 @@ -import { useCallback, useEffect, useState } from "react"; -import { faPlus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useNavigate } from "@tanstack/react-router"; +import { useCallback, useEffect } from "react"; import { createNotification } from "@app/components/notifications"; -import { Button, Checkbox, DeleteActionModal, Spinner } from "@app/components/v2"; -import { useOrganization, useProject } from "@app/context"; +import { Checkbox, DeleteActionModal, Spinner } from "@app/components/v2"; +import { useProject } from "@app/context"; import { usePopUp, useToggle } from "@app/hooks"; import { useDeleteIntegration, @@ -17,38 +14,26 @@ import { import { IntegrationAuth } from "@app/hooks/api/integrationAuth/types"; import { TIntegration } from "@app/hooks/api/integrations/types"; -import { redirectForProviderAuth } from "../../IntegrationsListPage.utils"; -import { CloudIntegrationSection } from "../CloudIntegrationSection"; import { IntegrationsTable } from "./IntegrationsTable"; -enum IntegrationView { - List = "list", - New = "new" -} - export const NativeIntegrationsTab = () => { - const { currentOrg } = useOrganization(); const { currentProject } = useProject(); const { environments, id: workspaceId } = currentProject; - const navigate = useNavigate(); const { data: cloudIntegrations, isPending: isCloudIntegrationsLoading } = useGetCloudIntegrations(); - const { - data: integrationAuths, - isPending: isIntegrationAuthLoading, - isFetching: isIntegrationAuthFetching - } = useGetWorkspaceAuthorizations( - workspaceId, - useCallback((data: IntegrationAuth[]) => { - const groupBy: Record = {}; - data.forEach((el) => { - groupBy[el.integration] = el; - }); - return groupBy; - }, []) - ); + const { data: integrationAuths, isFetching: isIntegrationAuthFetching } = + useGetWorkspaceAuthorizations( + workspaceId, + useCallback((data: IntegrationAuth[]) => { + const groupBy: Record = {}; + data.forEach((el) => { + groupBy[el.integration] = el; + }); + return groupBy; + }, []) + ); // mutation const { @@ -58,11 +43,8 @@ export const NativeIntegrationsTab = () => { } = useGetWorkspaceIntegrations(workspaceId); const { mutateAsync: deleteIntegration } = useDeleteIntegration(); - const { - mutateAsync: deleteIntegrationAuths, - isSuccess: isDeleteIntegrationAuthSuccess, - reset: resetDeleteIntegrationAuths - } = useDeleteIntegrationAuths(); + + const { reset: resetDeleteIntegrationAuths } = useDeleteIntegrationAuths(); const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).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 useEffect(() => { if ( - isDeleteIntegrationAuthSuccess && !isIntegrationFetching && !isIntegrationAuthFetching && isIntegrationsAuthorizedEmpty && @@ -81,29 +62,11 @@ export const NativeIntegrationsTab = () => { } }, [ isIntegrationFetching, - isDeleteIntegrationAuthSuccess, isIntegrationAuthFetching, isIntegrationsAuthorizedEmpty, 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 ( integrationId: string, 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([ "deleteConfirmation", "deleteSecretsConfirmation" ] as const); - const [view, setView] = useState(IntegrationView.List); - const [shouldDeleteSecrets, setShouldDeleteSecrets] = useToggle(false); if (isIntegrationLoading || isCloudIntegrationsLoading) @@ -150,18 +96,10 @@ export const NativeIntegrationsTab = () => { return ( <> - {view === IntegrationView.List ? ( + {integrations?.length && (

Native Integrations

-
{ }} />
- ) : ( - setView(IntegrationView.List)} - /> )}