From 322536d738671492bf5b6b2afb7461aad49942b5 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 3 Jan 2025 19:15:39 +0530 Subject: [PATCH] feat: completed migration of all integrations pages --- frontend-v2/src/const/routes.ts | 184 +- frontend-v2/src/helpers/localStorage.ts | 9 + .../src/hooks/api/integrationAuth/index.tsx | 1 + .../src/hooks/api/integrationAuth/queries.tsx | 20 +- .../organization/SshOverviewPage/route.tsx | 10 +- .../IntegrationsListPage.tsx | 4 +- .../IntegrationsListPage.utils.tsx | 258 +- .../IntegrationsListPage/route.tsx | 10 +- .../AwsParameterStoreAuthorizePage.tsx | 207 ++ .../AwsParameterStoreAuthorizePage/route.tsx | 9 + .../AwsParamterStoreConfigurePage.tsx | 391 +++ .../AwsParameterStoreConfigurePage/route.tsx | 16 + .../AwsSecretManagerAuthorizePage.tsx | 205 ++ .../AwsSecretManagerAuthorizePage/route.tsx | 9 + .../AwsSecretManagerConfigurePage.tsx | 544 ++++ .../AwsSecretManagerConfigurePage/route.tsx | 16 + .../AzureAppConfigurationConfigurePage.tsx | 325 +++ .../route.tsx | 16 + ...AzureAppConfigurationOauthCallbackPage.tsx | 46 + .../route.tsx | 17 + .../AzureDevopsAuthorizePage.tsx | 124 + .../AzureDevopsAuthorizePage/route.tsx | 9 + .../AzureDevopsConfigurePage.tsx | 192 ++ .../AzureDevopsConfigurePage/route.tsx | 16 + .../AzureKeyVaultConfigurePage.tsx | 166 ++ .../AzureKeyVaultConfigurePage/route.tsx | 16 + .../AzureKeyVaultOauthCallback.tsx | 46 + .../AzureKeyVaultOauthCallbackPage/route.tsx | 17 + .../BitbucketConfigurePage.tsx | 307 ++ .../BitbucketConfigurePage/route.tsx | 16 + .../BitbucketOauthCallbackPage.tsx | 45 + .../BitbucketOauthCallbackPage/route.tsx | 17 + .../ChecklyAuthorizePage.tsx | 115 + .../ChecklyAuthorizePage/route.tsx | 9 + .../ChecklyConfigurePage.tsx | 312 ++ .../ChecklyConfigurePage/route.tsx | 16 + .../CircleCIAuthorizePage.tsx | 108 + .../CircleCIAuthorizePage/route.tsx | 9 + .../CircleCIConfigurePage.tsx | 313 ++ .../CircleCIConfigurePage/route.tsx | 16 + .../Cloud66AuthorizePage.tsx | 72 + .../Cloud66AuthorizePage/route.tsx | 9 + .../Cloud66ConfigurePage.tsx | 151 + .../Cloud66ConfigurePage/route.tsx | 16 + .../CloudflarePagesAuthorizePage.tsx | 101 + .../CloudflarePagesAuthorizePage/route.tsx | 9 + .../CloudflarePagesConfigurePage.tsx | 219 ++ .../CloudflarePagesConfigurePage/route.tsx | 16 + .../CloudflareWorkersAuthorizePage.tsx | 101 + .../CloudflareWorkersAuthorizePage/route.tsx | 9 + .../CloudflareWorkersConfigurePage.tsx | 162 ++ .../CloudflareWorkersConfigurePage/route.tsx | 16 + .../CodefreshAuthorizePage.tsx | 71 + .../CodefreshAuthorizePage/route.tsx | 9 + .../CodefreshConfigurePage.tsx | 147 + .../CodefreshConfigurePage/route.tsx | 16 + .../DatabricksAuthorizePage.tsx | 130 + .../DatabricksAuthorizePage/route.tsx | 9 + .../DatabricksConfigurePage.tsx | 241 ++ .../DatabricksConfigurePage/route.tsx | 16 + .../DigitalOceanAppPlatformAuthorizePage.tsx | 71 + .../route.tsx | 9 + .../DigitalOceanAppPlatformConfigurePage.tsx | 148 + .../route.tsx | 16 + .../FlyioAuthorizePage/FlyioAuthorizePage.tsx | 120 + .../integrations/FlyioAuthorizePage/route.tsx | 9 + .../FlyioConfigurePage/FlyioConfigurePage.tsx | 237 ++ .../integrations/FlyioConfigurePage/route.tsx | 16 + .../GcpSecretManagerAuthorizePage.tsx | 170 ++ .../GcpSecretManagerAuthorizePage/route.tsx | 9 + .../GcpSecretManagerConfigurePage.tsx | 475 +++ .../GcpSecretManagerConfigurePage/route.tsx | 16 + .../GcpSecretManagerOauthCallbackPage.tsx | 46 + .../route.tsx | 17 + .../GithubAuthorizePage.tsx | 122 + .../GithubAuthorizePage/route.tsx | 9 + .../GithubConfigurePage.tsx | 817 ++++++ .../GithubConfigurePage/route.tsx | 16 + .../GithubOauthCallbackPage.tsx | 54 + .../GithubOauthCallbackPage/route.tsx | 18 + .../GitlabAuthorizePage.tsx | 121 + .../GitlabAuthorizePage/route.tsx | 9 + .../GitlabConfigurePage.tsx | 530 ++++ .../GitlabConfigurePage/route.tsx | 16 + .../GitlabOauthCallbackPage.tsx | 53 + .../GitlabOauthCallbackPage/route.tsx | 17 + .../HashicorpVaultAuthorizePage.tsx | 194 ++ .../HashicorpVaultAuthorizePage/route.tsx | 9 + .../HashicorpVaultConfigurePage.tsx | 291 ++ .../HashicorpVaultConfigurePage/route.tsx | 16 + .../HasuraCloudAuthorizePage.tsx | 123 + .../HasuraCloudAuthorizePage/route.tsx | 9 + .../HasuraCloudConfigurePage.tsx | 229 ++ .../HasuraCloudConfigurePage/route.tsx | 16 + .../HerokuConfigurePage.tsx | 294 ++ .../HerokuConfigurePage/route.tsx | 16 + .../HerokuOauthCallbackPage.tsx | 45 + .../HerokuOauthCallbackPage/route.tsx | 17 + .../LaravelForgeAuthorizePage.tsx | 95 + .../LaravelForgeAuthorizePage/route.tsx | 9 + .../LaravelForgeConfigurePage.tsx | 148 + .../LaravelForgeConfigurePage/route.tsx | 16 + .../NetlifyConfigurePage.tsx | 175 ++ .../NetlifyConfigurePage/route.tsx | 16 + .../NetlifyOauthCallbackPage.tsx | 46 + .../NetlifyOauthCallbackPage/route.tsx | 17 + .../NorthflankAuthorizePage.tsx | 71 + .../NorthflankAuthorizePage/route.tsx | 9 + .../NorthflankConfigurePage.tsx | 194 ++ .../NorthflankConfigurePage/route.tsx | 16 + .../OctopusDeployAuthorizePage.tsx | 143 + .../OctopusDeployAuthorizePage/route.tsx | 9 + .../OctopusDeployConfigurePage.tsx | 450 +++ .../OctopusDeployConfigurePage/route.tsx | 16 + .../QoveryAuthorizePage.tsx | 110 + .../QoveryAuthorizePage/route.tsx | 9 + .../QoveryConfigurePage.tsx | 431 +++ .../QoveryConfigurePage/route.tsx | 16 + .../RailwayAuthorizePage.tsx | 71 + .../RailwayAuthorizePage/route.tsx | 9 + .../RailwayConfigurePage.tsx | 232 ++ .../RailwayConfigurePage/route.tsx | 16 + .../RenderAuthorizePage.tsx | 109 + .../RenderAuthorizePage/route.tsx | 9 + .../RenderConfigurePage.tsx | 300 ++ .../RenderConfigurePage/route.tsx | 16 + .../RundeckAuthorizePage.tsx | 137 + .../RundeckAuthorizePage/route.tsx | 9 + .../RundeckConfigurePage.tsx | 222 ++ .../RundeckConfigurePage/route.tsx | 16 + .../SelectIntegrationAuthPage.tsx | 216 ++ .../SelectIntegrationAuthPage/route.tsx | 16 + .../SupabaseAuthorizePage.tsx | 71 + .../SupabaseAuthorizePage/route.tsx | 9 + .../SupabaseConfigurePage.tsx | 147 + .../SupabaseConfigurePage/route.tsx | 16 + .../TeamcityAuthorizePage.tsx | 134 + .../TeamcityAuthorizePage/route.tsx | 9 + .../TeamcityConfigurePage.tsx | 252 ++ .../TeamcityConfigurePage/route.tsx | 16 + .../TerraformCloudAuthorizePage.tsx | 136 + .../TerraformCloudAuthorizePage/route.tsx | 9 + .../TerraformCloudConfigurePage.tsx | 247 ++ .../TerraformCloudConfigurePage/route.tsx | 16 + .../TravisCIAuthorizePage.tsx | 71 + .../TravisCIAuthorizePage/route.tsx | 9 + .../TravisCIConfigurePage.tsx | 148 + .../TravisCIConfigurePage/route.tsx | 16 + .../VercelConfigurePage.tsx | 318 +++ .../VercelConfigurePage/route.tsx | 16 + .../VercelOauthCallbackPage.tsx | 46 + .../VercelOauthCallbackPage/route.tsx | 17 + .../WindmillAuthorizePage.tsx | 71 + .../WindmillAuthorizePage/route.tsx | 9 + .../WindmillConfigurePage.tsx | 149 + .../WindmillConfigurePage/route.tsx | 16 + ...zure-app-configurations-oauth-redirect.tsx | 29 + .../route-azure-key-vault-oauth-redirect.tsx | 29 + .../route-bitbucket-oauth-redirect.tsx | 29 + .../integrations/route-gcp-oauth-redirect.tsx | 29 + .../route-github-oauth-redirect.tsx | 29 + .../route-gitlab-oauth-redirect.tsx | 29 + .../route-heroku-oauth-redirect.tsx | 29 + .../route-netlify-oauth-redirect.tsx | 29 + .../route-vercel-oauth-redirect.tsx | 30 + frontend-v2/src/routeTree.gen.ts | 2538 ++++++++++++++++- frontend-v2/src/routes.ts | 219 +- 167 files changed, 18362 insertions(+), 65 deletions(-) create mode 100644 frontend-v2/src/helpers/localStorage.ts create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/AwsParameterStoreAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/AwsSecretManagerAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/AzureAppConfigurationConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/AzureAppConfigurationOauthCallbackPage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/AzureDevopsAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/AzureKeyVaultOauthCallback.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/BitbucketConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/BitbucketOauthCallbackPage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/ChecklyAuthorizePage/ChecklyAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/ChecklyAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/ChecklyConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CircleCIAuthorizePage/CircleCIAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CircleCIAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CircleCIConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/Cloud66AuthorizePage/Cloud66AuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/Cloud66AuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/Cloud66ConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/CloudflarePagesAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/CloudflareWorkersAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CodefreshAuthorizePage/CodefreshAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CodefreshAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/CodefreshConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/DatabricksAuthorizePage/DatabricksAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/DatabricksAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/DatabricksConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/DigitalOceanAppPlatformAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/FlyioAuthorizePage/FlyioAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/FlyioAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/FlyioConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GcpSecretManagerAuthorizePage/GcpSecretManagerAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GcpSecretManagerAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/GcpSecretManagerConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GcpSecretManagerOauthCallbackPage/GcpSecretManagerOauthCallbackPage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GcpSecretManagerOauthCallbackPage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GithubAuthorizePage/GithubAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GithubAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GithubConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GithubOauthCallbackPage/GithubOauthCallbackPage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GithubOauthCallbackPage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GitlabAuthorizePage/GitlabAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GitlabAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GitlabConfigurePage/GitlabConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GitlabConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GitlabOauthCallbackPage/GitlabOauthCallbackPage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/GitlabOauthCallbackPage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HasuraCloudAuthorizePage/HasuraCloudAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HasuraCloudAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/HasuraCloudConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HerokuConfigurePage/HerokuConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HerokuConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HerokuOauthCallbackPage/HerokuOauthCallbackPage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/HerokuOauthCallbackPage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/LaravelForgeAuthorizePage/LaravelForgeAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/LaravelForgeAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/LaravelForgeConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/NetlifyConfigurePage/NetlifyConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/NetlifyConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/NetlifyOauthCallbackPage/NetlifyOauthCallbackPage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/NetlifyOauthCallbackPage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/NorthflankAuthorizePage/NorthflankAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/NorthflankAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/NorthflankConfigurePage/NorthflankConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/NorthflankConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/OctopusDeployAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/QoveryAuthorizePage/QoveryAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/QoveryAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/QoveryConfigurePage/QoveryConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/QoveryConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RailwayAuthorizePage/RailwayAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RailwayAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RailwayConfigurePage/RailwayConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RailwayConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RenderAuthorizePage/RenderAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RenderAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RenderConfigurePage/RenderConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RenderConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RundeckAuthorizePage/RundeckAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RundeckAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RundeckConfigurePage/RundeckConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/RundeckConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/SelectIntegrationAuthPage/SelectIntegrationAuthPage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/SelectIntegrationAuthPage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/SupabaseAuthorizePage/SupabaseAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/SupabaseAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/SupabaseConfigurePage/SupabaseConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/SupabaseConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TeamcityAuthorizePage/TeamcityAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TeamcityAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TeamcityConfigurePage/TeamcityConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TeamcityConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TerraformCloudAuthorizePage/TerraformCloudAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TerraformCloudAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/TerraformCloudConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TravisCIAuthorizePage/TravisCIAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TravisCIAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TravisCIConfigurePage/TravisCIConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/TravisCIConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/VercelConfigurePage/VercelConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/VercelConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/VercelOauthCallbackPage/VercelOauthCallbackPage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/VercelOauthCallbackPage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/WindmillAuthorizePage/WindmillAuthorizePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/WindmillAuthorizePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/WindmillConfigurePage/WindmillConfigurePage.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/WindmillConfigurePage/route.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/route-azure-app-configurations-oauth-redirect.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/route-azure-key-vault-oauth-redirect.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/route-bitbucket-oauth-redirect.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/route-gcp-oauth-redirect.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/route-github-oauth-redirect.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/route-gitlab-oauth-redirect.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/route-heroku-oauth-redirect.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/route-netlify-oauth-redirect.tsx create mode 100644 frontend-v2/src/pages/secret-manager/integrations/route-vercel-oauth-redirect.tsx diff --git a/frontend-v2/src/const/routes.ts b/frontend-v2/src/const/routes.ts index 1299ebc12..221917b94 100644 --- a/frontend-v2/src/const/routes.ts +++ b/frontend-v2/src/const/routes.ts @@ -67,7 +67,189 @@ export const ROUTE_PATHS = Object.freeze({ IntegrationDetailsByIDPage: setRoute( "/secret-manager/$projectId/integrations/$integrationId", "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId" - ) + ), + Integratons: { + SelectIntegrationAuth: setRoute( + "/secret-manager/$projectId/integrations/select-integration-auth", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/select-integration-auth" + ), + HerokuOauthCallbackPage: setRoute( + "/secret-manager/$projectId/integrations/heroku/oauth2/callback", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/heroku/oauth2/callback" + ), + HerokuConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/heroku/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/heroku/create" + ), + AwsParameterStoreConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/aws-parameter-store/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/aws-parameter-store/create" + ), + AwsSecretManagerConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/aws-secret-manager/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/aws-secret-manager/create" + ), + AzureAppConfigurationsOauthCallbackPage: setRoute( + "/secret-manager/$projectId/integrations/azure-app-configuration/oauth2/callback", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-app-configuration/oauth2/callback" + ), + AzureAppConfigurationsConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/azure-app-configuration/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-app-configuration/create" + ), + AzureDevopsConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/azure-devops/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/create" + ), + AzureKeyVaultOauthCallbackPage: setRoute( + "/secret-manager/$projectId/integrations/azure-key-vault/oauth2/callback", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback" + ), + AzureKeyVaultConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/azure-key-vault/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/create" + ), + BitbucketOauthCallbackPage: setRoute( + "/secret-manager/$projectId/integrations/bitbucket/oauth2/callback", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/oauth2/callback" + ), + BitbucketConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/bitbucket/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/create" + ), + ChecklyConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/checkly/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/checkly/create" + ), + CircleConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/circleci/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/circleci/create" + ), + CloudflarePagesConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/cloudflare-pages/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-pages/create" + ), + DigitalOceanAppPlatformConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/digital-ocean-app-platform/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/digital-ocean-app-platform/create" + ), + CloudflareWorkersConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/cloudflare-workers/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-workers/create" + ), + CodefreshConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/codefresh/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/codefresh/create" + ), + GcpSecretManagerConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/gcp-secret-manager/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/create" + ), + GcpSecretManagerOauthCallbackPage: setRoute( + "/secret-manager/$projectId/integrations/gcp-secret-manager/oauth2/callback", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/oauth2/callback" + ), + GithubConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/github/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/github/create" + ), + GithubOauthCallbackPage: setRoute( + "/secret-manager/$projectId/integrations/github/oauth2/callback", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/github/oauth2/callback" + ), + GitlabConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/gitlab/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/gitlab/create" + ), + GitlabOauthCallbackPage: setRoute( + "/secret-manager/$projectId/integrations/gitlab/oauth2/callback", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/gitlab/oauth2/callback" + ), + VercelOauthCallbackPage: setRoute( + "/secret-manager/$projectId/integrations/vercel/oauth2/callback", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/vercel/oauth2/callback" + ), + VercelConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/vercel/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/vercel/create" + ), + FlyioConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/flyio/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/flyio/create" + ), + HashicorpVaultConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/hashicorp-vault/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/hashicorp-vault/create" + ), + HasuraCloudConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/hasura-cloud/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/hasura-cloud/create" + ), + LaravelForgeConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/laravel-forge/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/laravel-forge/create" + ), + NorthflankConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/northflank/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/northflank/create" + ), + RailwayConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/railway/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/railway/create" + ), + RenderConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/render/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/render/create" + ), + RundeckConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/rundeck/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/rundeck/create" + ), + WindmillConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/windmill/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/windmill/create" + ), + TravisCIConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/travisci/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/travisci/create" + ), + TerraformCloudConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/terraform-cloud/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/terraform-cloud/create" + ), + TeamcityConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/teamcity/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/teamcity/create" + ), + SupabaseConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/supabase/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/supabase/create" + ), + OctopusDeployCloudConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/octopus-deploy/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/octopus-deploy/create" + ), + DatabricksConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/databricks/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/databricks/create" + ), + QoveryConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/qovery/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/qovery/create" + ), + Cloud66ConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/cloud-66/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/cloud-66/create" + ), + NetlifyConfigurePage: setRoute( + "/secret-manager/$projectId/integrations/netlify/create", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/netlify/create" + ), + NetlifyOuathCallbackPage: setRoute( + "/secret-manager/$projectId/integrations/netlify/oauth2/callback", + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/netlify/oauth2/callback" + ) + } }, CertManager: { CertAuthDetailsByIDPage: setRoute( diff --git a/frontend-v2/src/helpers/localStorage.ts b/frontend-v2/src/helpers/localStorage.ts new file mode 100644 index 000000000..7a265d18e --- /dev/null +++ b/frontend-v2/src/helpers/localStorage.ts @@ -0,0 +1,9 @@ +const INTEGRATION_PROJECT_ID = "integration_project_id"; +export const localStorageService = { + getIintegrationProjectId() { + return localStorage.getItem(INTEGRATION_PROJECT_ID); + }, + setIntegrationProjectId(projectId: string) { + return localStorage.setItem(INTEGRATION_PROJECT_ID, projectId); + } +}; diff --git a/frontend-v2/src/hooks/api/integrationAuth/index.tsx b/frontend-v2/src/hooks/api/integrationAuth/index.tsx index 0ae3511de..e7ee5928a 100644 --- a/frontend-v2/src/hooks/api/integrationAuth/index.tsx +++ b/frontend-v2/src/hooks/api/integrationAuth/index.tsx @@ -7,6 +7,7 @@ export { useGetIntegrationAuthBitBucketWorkspaces, useGetIntegrationAuthById, useGetIntegrationAuthChecklyGroups, + useGetIntegrationAuthCircleCIOrganizations, useGetIntegrationAuthGithubEnvs, useGetIntegrationAuthGithubOrgs, useGetIntegrationAuthNorthflankSecretGroups, diff --git a/frontend-v2/src/hooks/api/integrationAuth/queries.tsx b/frontend-v2/src/hooks/api/integrationAuth/queries.tsx index ed5a91365..44b199a24 100644 --- a/frontend-v2/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend-v2/src/hooks/api/integrationAuth/queries.tsx @@ -9,6 +9,7 @@ import { BitBucketEnvironment, BitBucketWorkspace, ChecklyGroup, + CircleCIOrganization, Environment, HerokuPipelineCoupling, IntegrationAuth, @@ -129,7 +130,9 @@ const integrationAuthKeys = { integrationAuthId, ...params }: TGetIntegrationAuthOctopusDeployScopeValuesDTO) => - [{ integrationAuthId }, "getIntegrationAuthOctopusDeployScopeValues", params] as const + [{ integrationAuthId }, "getIntegrationAuthOctopusDeployScopeValues", params] as const, + getIntegrationAuthCircleCIOrganizations: (integrationAuthId: string) => + [{ integrationAuthId }, "getIntegrationAuthCircleCIOrganizations"] as const }; const fetchIntegrationAuthById = async (integrationAuthId: string) => { @@ -835,6 +838,21 @@ export const useGetIntegrationAuthBitBucketEnvironments = ( }); }; +const fetchIntegrationAuthCircleCIOrganizations = async (integrationAuthId: string) => { + const { + data: { organizations } + } = await apiRequest.get<{ + organizations: CircleCIOrganization[]; + }>(`/api/v1/integration-auth/${integrationAuthId}/circleci/organizations`); + return organizations; +}; +export const useGetIntegrationAuthCircleCIOrganizations = (integrationAuthId: string) => { + return useQuery({ + queryKey: integrationAuthKeys.getIntegrationAuthCircleCIOrganizations(integrationAuthId), + queryFn: () => fetchIntegrationAuthCircleCIOrganizations(integrationAuthId) + }); +}; + export const useGetIntegrationAuthNorthflankSecretGroups = ({ integrationAuthId, appId diff --git a/frontend-v2/src/pages/organization/SshOverviewPage/route.tsx b/frontend-v2/src/pages/organization/SshOverviewPage/route.tsx index 8d08e049d..4fd99a812 100644 --- a/frontend-v2/src/pages/organization/SshOverviewPage/route.tsx +++ b/frontend-v2/src/pages/organization/SshOverviewPage/route.tsx @@ -1,9 +1,9 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute } from "@tanstack/react-router"; -import { SshOverviewPage } from './SshOverviewPage' +import { SshOverviewPage } from "./SshOverviewPage"; export const Route = createFileRoute( - '/_authenticate/_inject-org-details/organization/_layout/ssh/overview', + "/_authenticate/_inject-org-details/organization/_layout/ssh/overview" )({ - component: SshOverviewPage, -}) + component: SshOverviewPage +}); diff --git a/frontend-v2/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx b/frontend-v2/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx index 2fca1ca83..5d87bbcf6 100644 --- a/frontend-v2/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx +++ b/frontend-v2/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useState } from "react"; import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; +import { useNavigate } from "@tanstack/react-router"; import { motion } from "framer-motion"; import { createNotification } from "@app/components/notifications"; @@ -29,6 +30,7 @@ enum IntegrationView { const Page = () => { const { currentWorkspace } = useWorkspace(); + const navigate = useNavigate(); const { environments, id: workspaceId } = currentWorkspace; const [view, setView] = useState(IntegrationView.New); @@ -97,7 +99,7 @@ const Page = () => { if (!selectedCloudIntegration) return; try { - redirectForProviderAuth(selectedCloudIntegration); + redirectForProviderAuth(currentWorkspace.id, navigate, selectedCloudIntegration); } catch (error) { console.error(error); } diff --git a/frontend-v2/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx b/frontend-v2/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx index ce31f743f..439ada69d 100644 --- a/frontend-v2/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx +++ b/frontend-v2/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.utils.tsx @@ -1,10 +1,13 @@ import crypto from "crypto"; +import { NavigateFn } from "@tanstack/react-router"; + import { createNotification } from "@app/components/notifications"; import { decryptAssymmetric, encryptAssymmetric } from "@app/components/utilities/cryptography/crypto"; +import { localStorageService } from "@app/helpers/localStorage"; import { TCloudIntegration, UserWsKeyPair } from "@app/hooks/api/types"; export const generateBotKey = (botPublicKey: string, latestKey: UserWsKeyPair) => { @@ -52,154 +55,309 @@ export const createIntegrationMissingEnvVarsNotification = ( title: "Missing Environment Variables" }); -export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => { +export const redirectForProviderAuth = ( + 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); - let link = ""; switch (integrationOption.slug) { case "gcp-secret-manager": - link = `${window.location.origin}/integrations/gcp-secret-manager/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/gcp-secret-manager/authorize", + params: { + projectId + } + }); break; - case "azure-key-vault": + case "azure-key-vault": { if (!integrationOption.clientId) { createIntegrationMissingEnvVarsNotification(integrationOption.slug); return; } - link = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/azure-key-vault/oauth2/callback&response_mode=query&scope=https://vault.azure.net/.default openid offline_access&state=${state}`; + 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-key-vault/oauth2/callback&response_mode=query&scope=https://vault.azure.net/.default openid offline_access&state=${state}`; + window.location.assign(link); break; - case "azure-app-configuration": + } + case "azure-app-configuration": { if (!integrationOption.clientId) { createIntegrationMissingEnvVarsNotification(integrationOption.slug); return; } - 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}`; + 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": - link = `${window.location.origin}/integrations/aws-parameter-store/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/aws-parameter-store/authorize", + params: { + projectId + } + }); break; case "aws-secret-manager": - link = `${window.location.origin}/integrations/aws-secret-manager/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/aws-secret-manager/authorize", + params: { + projectId + } + }); break; - case "heroku": + case "heroku": { if (!integrationOption.clientId) { createIntegrationMissingEnvVarsNotification(integrationOption.slug); return; } - link = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`; + 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": + } + case "vercel": { if (!integrationOption.clientSlug) { createIntegrationMissingEnvVarsNotification(integrationOption.slug); return; } - link = `https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`; + const link = `https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`; + window.location.assign(link); break; - case "netlify": + } + case "netlify": { if (!integrationOption.clientId) { createIntegrationMissingEnvVarsNotification(integrationOption.slug); return; } - link = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/integrations/netlify/oauth2/callback`; + 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": - link = `${window.location.origin}/integrations/github/auth-mode-selection`; + navigate({ + to: "/secret-manager/$projectId/integrations/github/auth-mode-selection", + params: { + projectId + } + }); break; case "gitlab": - link = `${window.location.origin}/integrations/gitlab/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/gitlab/authorize", + params: { + projectId + } + }); break; case "render": - link = `${window.location.origin}/integrations/render/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/render/authorize", + params: { + projectId + } + }); break; case "flyio": - link = `${window.location.origin}/integrations/flyio/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/flyio/authorize", + params: { + projectId + } + }); break; case "circleci": - link = `${window.location.origin}/integrations/circleci/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/circleci/authorize", + params: { + projectId + } + }); break; case "databricks": - link = `${window.location.origin}/integrations/databricks/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/databricks/authorize", + params: { + projectId + } + }); break; case "laravel-forge": - link = `${window.location.origin}/integrations/laravel-forge/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/laravel-forge/authorize", + params: { + projectId + } + }); break; case "travisci": - link = `${window.location.origin}/integrations/travisci/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/travisci/authorize", + params: { + projectId + } + }); break; case "supabase": - link = `${window.location.origin}/integrations/supabase/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/supabase/authorize", + params: { + projectId + } + }); break; case "checkly": - link = `${window.location.origin}/integrations/checkly/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/checkly/authorize", + params: { + projectId + } + }); break; case "qovery": - link = `${window.location.origin}/integrations/qovery/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/qovery/authorize", + params: { + projectId + } + }); break; case "railway": - link = `${window.location.origin}/integrations/railway/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/railway/authorize", + params: { + projectId + } + }); break; case "terraform-cloud": - link = `${window.location.origin}/integrations/terraform-cloud/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/terraform-cloud/authorize", + params: { + projectId + } + }); break; case "hashicorp-vault": - link = `${window.location.origin}/integrations/hashicorp-vault/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/hashicorp-vault/authorize", + params: { + projectId + } + }); break; case "cloudflare-pages": - link = `${window.location.origin}/integrations/cloudflare-pages/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/cloudflare-pages/authorize", + params: { + projectId + } + }); break; case "cloudflare-workers": - link = `${window.location.origin}/integrations/cloudflare-workers/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/cloudflare-workers/authorize", + params: { + projectId + } + }); break; - case "bitbucket": + case "bitbucket": { if (!integrationOption.clientId) { createIntegrationMissingEnvVarsNotification(integrationOption.slug, "cicd"); return; } - 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}`; + 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": - link = `${window.location.origin}/integrations/codefresh/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/codefresh/authorize", + params: { + projectId + } + }); break; case "digital-ocean-app-platform": - link = `${window.location.origin}/integrations/digital-ocean-app-platform/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/digital-ocean-app-platform/authorize", + params: { + projectId + } + }); break; case "cloud-66": - link = `${window.location.origin}/integrations/cloud-66/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/cloud-66/authorize", + params: { + projectId + } + }); break; case "northflank": - link = `${window.location.origin}/integrations/northflank/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/northflank/authorize", + params: { + projectId + } + }); break; case "windmill": - link = `${window.location.origin}/integrations/windmill/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/windmill/authorize", + params: { + projectId + } + }); break; case "teamcity": - link = `${window.location.origin}/integrations/teamcity/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/teamcity/authorize", + params: { + projectId + } + }); break; case "hasura-cloud": - link = `${window.location.origin}/integrations/hasura-cloud/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/hasura-cloud/authorize", + params: { + projectId + } + }); break; case "rundeck": - link = `${window.location.origin}/integrations/rundeck/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/rundeck/authorize", + params: { + projectId + } + }); break; case "azure-devops": - link = `${window.location.origin}/integrations/azure-devops/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/azure-devops/authorize", + params: { + projectId + } + }); break; case "octopus-deploy": - link = `${window.location.origin}/integrations/octopus-deploy/authorize`; + navigate({ + to: "/secret-manager/$projectId/integrations/octopus-deploy/authorize", + params: { + projectId + } + }); break; default: break; } - - if (link !== "") { - window.location.assign(link); - } } catch (err) { console.error(err); } }; - -export const redirectToIntegrationAppConfigScreen = (provider: string, integrationAuthId: string) => - `/integrations/${provider}/create?integrationAuthId=${integrationAuthId}`; diff --git a/frontend-v2/src/pages/secret-manager/IntegrationsListPage/route.tsx b/frontend-v2/src/pages/secret-manager/IntegrationsListPage/route.tsx index 7012affc6..fbea3d346 100644 --- a/frontend-v2/src/pages/secret-manager/IntegrationsListPage/route.tsx +++ b/frontend-v2/src/pages/secret-manager/IntegrationsListPage/route.tsx @@ -1,9 +1,9 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute } from "@tanstack/react-router"; -import { IntegrationsListPage } from './IntegrationsListPage' +import { IntegrationsListPage } from "./IntegrationsListPage"; export const Route = createFileRoute( - '/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/', + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/" )({ - component: IntegrationsListPage, -}) + component: IntegrationsListPage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/AwsParameterStoreAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/AwsParameterStoreAuthorizePage.tsx new file mode 100644 index 000000000..c7fb512eb --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/AwsParameterStoreAuthorizePage.tsx @@ -0,0 +1,207 @@ +import { Helmet } from "react-helmet"; +import { Controller, useForm } from "react-hook-form"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useNavigate } from "@tanstack/react-router"; +import { z } from "zod"; + +import { + Button, + Card, + CardBody, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +enum AwsAuthType { + AccessKey = "access-key", + AssumeRole = "assume-role" +} + +const formSchema = z.discriminatedUnion("type", [ + z.object({ + type: z.literal(AwsAuthType.AccessKey), + accessKey: z.string().min(1), + accessSecretKey: z.string().min(1) + }), + z.object({ + type: z.literal(AwsAuthType.AssumeRole), + iamRoleArn: z.string().min(1) + }) +]); + +type TForm = z.infer; + +export const AWSParameterStoreAuthorizeIntegrationPage = () => { + const navigate = useNavigate(); + const { currentWorkspace } = useWorkspace(); + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const { control, handleSubmit, formState, watch } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + type: AwsAuthType.AccessKey + } + }); + + const formAwsAuthTypeField = watch("type"); + + const handleFormSubmit = async (data: TForm) => { + try { + const integrationAuth = await mutateAsync({ + workspaceId: localStorage.getItem("projectData.id"), + integration: "aws-parameter-store", + ...(data.type === AwsAuthType.AssumeRole + ? { + awsAssumeIamRoleArn: data.iamRoleArn + } + : { + accessId: data.accessKey, + accessToken: data.accessSecretKey + }) + }); + + navigate({ + to: "/secret-manager/$projectId/integrations/aws-parameter-store/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Authorize AWS Parameter Integration + + + +
+
+ AWS logo +
+ AWS Parameter Store Integration + +
+ + Docs + +
+
+
+
+ +
+ ( + + + + )} + /> + {formAwsAuthTypeField === AwsAuthType.AccessKey ? ( + <> + ( + + + + )} + /> + ( + + + + )} + /> + + ) : ( + ( + + + + )} + /> + )} + + +
+
+
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/route.tsx new file mode 100644 index 000000000..9053974c7 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AWSParameterStoreAuthorizeIntegrationPage } from "./AwsParameterStoreAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/aws-parameter-store/authorize" +)({ + component: AWSParameterStoreAuthorizeIntegrationPage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx new file mode 100644 index 000000000..3bf787763 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx @@ -0,0 +1,391 @@ +import { useEffect, useState } from "react"; +import { Helmet } from "react-helmet"; +import { + faArrowUpRightFromSquare, + faBookOpen, + faBugs, + faCircleInfo +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate, useSearch } from "@tanstack/react-router"; +import { motion } from "framer-motion"; + +import { + Badge, + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem, + Switch, + Tab, + TabList, + TabPanel, + Tabs +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries"; + +enum TabSections { + Connection = "connection", + Options = "options" +} + +const awsRegions = [ + { name: "US East (Ohio)", slug: "us-east-2" }, + { name: "US East (N. Virginia)", slug: "us-east-1" }, + { name: "US West (N. California)", slug: "us-west-1" }, + { name: "US West (Oregon)", slug: "us-west-2" }, + { name: "Africa (Cape Town)", slug: "af-south-1" }, + { name: "Asia Pacific (Hong Kong)", slug: "ap-east-1" }, + { name: "Asia Pacific (Hyderabad)", slug: "ap-south-2" }, + { name: "Asia Pacific (Jakarta)", slug: "ap-southeast-3" }, + { name: "Asia Pacific (Melbourne)", slug: "ap-southeast-4" }, + { name: "Asia Pacific (Mumbai)", slug: "ap-south-1" }, + { name: "Asia Pacific (Osaka)", slug: "ap-northeast-3" }, + { name: "Asia Pacific (Seoul)", slug: "ap-northeast-2" }, + { name: "Asia Pacific (Singapore)", slug: "ap-southeast-1" }, + { name: "Asia Pacific (Sydney)", slug: "ap-southeast-2" }, + { name: "Asia Pacific (Tokyo)", slug: "ap-northeast-1" }, + { name: "Canada (Central)", slug: "ca-central-1" }, + { name: "Europe (Frankfurt)", slug: "eu-central-1" }, + { name: "Europe (Ireland)", slug: "eu-west-1" }, + { name: "Europe (London)", slug: "eu-west-2" }, + { name: "Europe (Milan)", slug: "eu-south-1" }, + { name: "Europe (Paris)", slug: "eu-west-3" }, + { name: "Europe (Spain)", slug: "eu-south-2" }, + { name: "Europe (Stockholm)", slug: "eu-north-1" }, + { name: "Europe (Zurich)", slug: "eu-central-2" }, + { name: "Middle East (Bahrain)", slug: "me-south-1" }, + { name: "Middle East (UAE)", slug: "me-central-1" }, + { name: "South America (Sao Paulo)", slug: "sa-east-1" }, + { name: "AWS GovCloud (US-East)", slug: "us-gov-east-1" }, + { name: "AWS GovCloud (US-West)", slug: "us-gov-west-1" } +]; + +export const AWSParameterStoreConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useCreateIntegration(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.AwsParameterStoreConfigurePage.id, + select: (el) => el.integrationAuthId + }); + const { currentWorkspace } = useWorkspace(); + + const { data: integrationAuth, isPending: isintegrationAuthLoading } = useGetIntegrationAuthById( + (integrationAuthId as string) ?? "" + ); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + const [selectedAWSRegion, setSelectedAWSRegion] = useState(""); + const [path, setPath] = useState(""); + const [pathErrorText, setPathErrorText] = useState(""); + + const [isLoading, setIsLoading] = useState(false); + const [shouldTag, setShouldTag] = useState(false); + const [shouldDisableDelete, setShouldDisableDelete] = useState(false); + const [tagKey, setTagKey] = useState(""); + const [tagValue, setTagValue] = useState(""); + const [kmsKeyId, setKmsKeyId] = useState(""); + + useEffect(() => { + if (currentWorkspace) { + setSelectedSourceEnvironment(currentWorkspace.environments[0].slug); + setSelectedAWSRegion(awsRegions[0].slug); + } + }, [currentWorkspace]); + + const { data: integrationAuthAwsKmsKeys, isPending: isIntegrationAuthAwsKmsKeysLoading } = + useGetIntegrationAuthAwsKmsKeys({ + integrationAuthId: String(integrationAuthId), + region: selectedAWSRegion + }); + + const isValidAWSParameterStorePath = (awsStorePath: string) => { + const pattern = /^\/([\w-]+\/)*[\w-]+\/$/; + return pattern.test(awsStorePath) && awsStorePath.length <= 2048; + }; + + const handleButtonClick = async () => { + try { + if (path !== "") { + // case: path is not empty + if (!isValidAWSParameterStorePath(path)) { + // case: path is not valid for aws parameter store + setPathErrorText("Path must be a valid path for SSM like /project/env/"); + return; + } + } + + if (!integrationAuth?.id) return; + + setIsLoading(true); + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + sourceEnvironment: selectedSourceEnvironment, + path, + region: selectedAWSRegion, + secretPath, + metadata: { + ...(shouldTag + ? { + secretAWSTag: [ + { + key: tagKey, + value: tagValue + } + ] + } + : {}), + ...(kmsKeyId && { kmsKeyId }), + ...(shouldDisableDelete && { shouldDisableDelete }) + } + }); + + setIsLoading(false); + setPathErrorText(""); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && selectedSourceEnvironment && !isIntegrationAuthAwsKmsKeysLoading ? ( +
+ + Set Up AWS Parameter Integration + + + +
+
+ AWS logo +
+ AWS Parameter Store Integration + +
+ + Docs + +
+
+
+
+ + +
+ Connection + Options +
+
+ + + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + + setPath(e.target.value)} + /> + + + + + +
+ + Disable deleting secrets in AWS Parameter Store + +
+
+ + Tag in AWS Parameter Store + +
+ {shouldTag && ( +
+ + setTagKey(e.target.value)} + /> + + + setTagValue(e.target.value)} + /> + +
+ )} + + + +
+
+
+ +
+
+
+
+ {" "} + Pro Tip +
+ + After creating an integration, your secrets will start syncing immediately. This might + cause an unexpected override of current secrets in AWS Parameter Store with secrets from + Infisical. + +
+
+ ) : ( +
+ + Set Up AWS Parameter Store Integration + + {isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading ? ( + infisical loading indicator + ) : ( +
+ +

+ Something went wrong. Please contact{" "} + + support@infisical.com + {" "} + if the issue persists. +

+
+ )} +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/route.tsx new file mode 100644 index 000000000..3b8c4a3ea --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { AWSParameterStoreConfigurePage } from "./AwsParamterStoreConfigurePage"; + +const AwsParameterStoreConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/aws-parameter-store/create" +)({ + component: AWSParameterStoreConfigurePage, + validateSearch: zodValidator(AwsParameterStoreConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/AwsSecretManagerAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/AwsSecretManagerAuthorizePage.tsx new file mode 100644 index 000000000..355283170 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/AwsSecretManagerAuthorizePage.tsx @@ -0,0 +1,205 @@ +import { Helmet } from "react-helmet"; +import { Controller, useForm } from "react-hook-form"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useNavigate } from "@tanstack/react-router"; +import { z } from "zod"; + +import { + Button, + Card, + CardBody, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +enum AwsAuthType { + AccessKey = "access-key", + AssumeRole = "assume-role" +} + +const formSchema = z.discriminatedUnion("type", [ + z.object({ + type: z.literal(AwsAuthType.AccessKey), + accessKey: z.string().min(1), + accessSecretKey: z.string().min(1) + }), + z.object({ + type: z.literal(AwsAuthType.AssumeRole), + iamRoleArn: z.string().min(1) + }) +]); + +type TForm = z.infer; + +export const AWSSecretManagerAuthorizePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useSaveIntegrationAccessToken(); + const { currentWorkspace } = useWorkspace(); + + const { control, handleSubmit, formState, watch } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + type: AwsAuthType.AccessKey + } + }); + const formAwsAuthTypeField = watch("type"); + + const handleFormSubmit = async (data: TForm) => { + try { + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "aws-secret-manager", + ...(data.type === AwsAuthType.AssumeRole + ? { + awsAssumeIamRoleArn: data.iamRoleArn + } + : { + accessId: data.accessKey, + accessToken: data.accessSecretKey + }) + }); + navigate({ + to: "/secret-manager/$projectId/integrations/aws-secret-manager/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Authorize AWS Secrets Manager Integration + + + +
+
+ AWS logo +
+ AWS Secrets Manager Integration + +
+ + Docs + +
+
+
+
+ +
+ ( + + + + )} + /> + {formAwsAuthTypeField === AwsAuthType.AccessKey ? ( + <> + ( + + + + )} + /> + ( + + + + )} + /> + + ) : ( + ( + + + + )} + /> + )} + + +
+
+
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/route.tsx new file mode 100644 index 000000000..cf0172a4c --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AWSSecretManagerAuthorizePage } from "./AwsSecretManagerAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/aws-secret-manager/authorize" +)({ + component: AWSSecretManagerAuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx new file mode 100644 index 000000000..0ba29ace5 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx @@ -0,0 +1,544 @@ +import { useEffect } from "react"; +import { Helmet } from "react-helmet"; +import { Controller, useForm } from "react-hook-form"; +import { + faArrowUpRightFromSquare, + faBookOpen, + faBugs, + faCircleInfo +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useNavigate, useSearch } from "@tanstack/react-router"; +import { motion } from "framer-motion"; +import z from "zod"; + +import { + Badge, + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem, + Switch, + Tab, + TabList, + TabPanel, + Tabs +} from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries"; +import { IntegrationMappingBehavior } from "@app/hooks/api/integrations/types"; + +enum TabSections { + Connection = "connection", + Options = "options" +} + +const awsRegions = [ + { name: "US East (Ohio)", slug: "us-east-2" }, + { name: "US East (N. Virginia)", slug: "us-east-1" }, + { name: "US West (N. California)", slug: "us-west-1" }, + { name: "US West (Oregon)", slug: "us-west-2" }, + { name: "Africa (Cape Town)", slug: "af-south-1" }, + { name: "Asia Pacific (Hong Kong)", slug: "ap-east-1" }, + { name: "Asia Pacific (Hyderabad)", slug: "ap-south-2" }, + { name: "Asia Pacific (Jakarta)", slug: "ap-southeast-3" }, + { name: "Asia Pacific (Melbourne)", slug: "ap-southeast-4" }, + { name: "Asia Pacific (Mumbai)", slug: "ap-south-1" }, + { name: "Asia Pacific (Osaka)", slug: "ap-northeast-3" }, + { name: "Asia Pacific (Seoul)", slug: "ap-northeast-2" }, + { name: "Asia Pacific (Singapore)", slug: "ap-southeast-1" }, + { name: "Asia Pacific (Sydney)", slug: "ap-southeast-2" }, + { name: "Asia Pacific (Tokyo)", slug: "ap-northeast-1" }, + { name: "Canada (Central)", slug: "ca-central-1" }, + { name: "Europe (Frankfurt)", slug: "eu-central-1" }, + { name: "Europe (Ireland)", slug: "eu-west-1" }, + { name: "Europe (London)", slug: "eu-west-2" }, + { name: "Europe (Milan)", slug: "eu-south-1" }, + { name: "Europe (Paris)", slug: "eu-west-3" }, + { name: "Europe (Spain)", slug: "eu-south-2" }, + { name: "Europe (Stockholm)", slug: "eu-north-1" }, + { name: "Europe (Zurich)", slug: "eu-central-2" }, + { name: "Middle East (Bahrain)", slug: "me-south-1" }, + { name: "Middle East (UAE)", slug: "me-central-1" }, + { name: "South America (Sao Paulo)", slug: "sa-east-1" }, + { name: "AWS GovCloud (US-East)", slug: "us-gov-east-1" }, + { name: "AWS GovCloud (US-West)", slug: "us-gov-west-1" } +]; + +const mappingBehaviors = [ + { + label: "Many to One (All Infisical secrets will be mapped to a single AWS secret)", + value: IntegrationMappingBehavior.MANY_TO_ONE + }, + { + label: "One to One - (Each Infisical secret will be mapped to its own AWS secret)", + value: IntegrationMappingBehavior.ONE_TO_ONE + } +]; + +const schema = z + .object({ + awsRegion: z.string().trim().min(1, { message: "AWS region is required" }), + secretPath: z.string().trim().min(1, { message: "Secret path is required" }), + sourceEnvironment: z.string().trim().min(1, { message: "Source environment is required" }), + secretPrefix: z.string().default(""), + secretName: z.string().trim().min(1).optional(), + mappingBehavior: z.nativeEnum(IntegrationMappingBehavior), + kmsKeyId: z.string().optional(), + shouldTag: z.boolean().optional(), + tags: z + .object({ + key: z.string(), + value: z.string() + }) + .array() + }) + .refine( + (val) => + val.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE || + (val.mappingBehavior === IntegrationMappingBehavior.MANY_TO_ONE && + val.secretName && + val.secretName !== ""), + { + message: "Secret name must be defined for many-to-one integrations", + path: ["secretName"] + } + ); + +type TFormSchema = z.infer; + +export const AwsSecretManagerConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useCreateIntegration(); + const { + control, + setValue, + handleSubmit, + watch, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + shouldTag: false, + secretPath: "/", + secretPrefix: "", + mappingBehavior: IntegrationMappingBehavior.MANY_TO_ONE, + tags: [] + } + }); + + const shouldTagState = watch("shouldTag"); + const selectedSourceEnvironment = watch("sourceEnvironment"); + const selectedAWSRegion = watch("awsRegion"); + const selectedMappingBehavior = watch("mappingBehavior"); + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.AwsSecretManagerConfigurePage.id, + select: (el) => el.integrationAuthId + }); + + const { currentWorkspace } = useWorkspace(); + const { data: integrationAuth, isLoading: isintegrationAuthLoading } = useGetIntegrationAuthById( + (integrationAuthId as string) ?? "" + ); + + const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } = + useGetIntegrationAuthAwsKmsKeys({ + integrationAuthId: String(integrationAuthId), + region: selectedAWSRegion + }); + + useEffect(() => { + if (currentWorkspace) { + setValue("sourceEnvironment", currentWorkspace.environments[0].slug); + setValue("awsRegion", awsRegions[0].slug); + } + }, [currentWorkspace]); + + const handleButtonClick = async ({ + secretName, + sourceEnvironment, + awsRegion, + secretPath, + shouldTag, + tags, + secretPrefix, + kmsKeyId, + mappingBehavior + }: TFormSchema) => { + try { + if (!integrationAuth?.id) return; + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: secretName, + sourceEnvironment, + region: awsRegion, + secretPath, + metadata: { + ...(shouldTag + ? { + secretAWSTag: tags + } + : {}), + ...(secretPrefix && { secretPrefix }), + ...(kmsKeyId && { kmsKeyId }), + mappingBehavior + } + }); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && selectedSourceEnvironment && !isIntegrationAuthAwsKmsKeysLoading ? ( +
+ + Set Up AWS Secrets Manager Integration + +
+ + +
+
+ AWS logo +
+ AWS Secrets Manager Integration + +
+ + Docs + +
+
+
+
+ + +
+ Connection + Options +
+
+ + + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + {selectedMappingBehavior === IntegrationMappingBehavior.MANY_TO_ONE && ( + ( + + + + )} + /> + )} + + + + +
+ ( + onChange(isChecked)} + isChecked={value} + > + Tag in AWS Secrets Manager + + )} + /> +
+ {shouldTagState && ( +
+ ( + + + + )} + /> + ( + + + + )} + /> +
+ )} + + ( + + + + )} + /> + + ( + + + + )} + /> +
+
+
+ +
+
+
+
+ {" "} + Pro Tip +
+ + After creating an integration, your secrets will start syncing immediately. This might + cause an unexpected override of current secrets in AWS Secrets Manager with secrets from + Infisical. + +
+ +
+ ) : ( +
+ + Set Up AWS Secrets Manager Integration + + {isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading ? ( + infisical loading indicator + ) : ( +
+ +

+ Something went wrong. Please contact{" "} + + support@infisical.com + {" "} + if the issue persists. +

+
+ )} +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/route.tsx new file mode 100644 index 000000000..2d8d0dafd --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { AwsSecretManagerConfigurePage } from "./AwsSecretManagerConfigurePage"; + +const AwsSecretManagerConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/aws-secret-manager/create" +)({ + component: AwsSecretManagerConfigurePage, + validateSearch: zodValidator(AwsSecretManagerConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/AzureAppConfigurationConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/AzureAppConfigurationConfigurePage.tsx new file mode 100644 index 000000000..a5c4538f3 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/AzureAppConfigurationConfigurePage.tsx @@ -0,0 +1,325 @@ +import { useEffect } from "react"; +import { Helmet } from "react-helmet"; +import { Controller, useForm } from "react-hook-form"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useNavigate, useSearch } from "@tanstack/react-router"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Card, + CardTitle, + FormControl, + FormLabel, + Input, + Select, + SelectItem, + Switch +} from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; +import { useGetWorkspaceById } from "@app/hooks/api/workspace"; + +const schema = z.object({ + baseUrl: z + .string() + .trim() + .min(1, { message: "Azure App Configuration URL is required" }) + .url() + .refine( + (val) => val.endsWith(".azconfig.io"), + "URL should have the following format: https://resource-name-here.azconfig.io" + ), + secretPath: z.string().trim().min(1, { message: "Secret path is required" }), + sourceEnvironment: z.string().trim().min(1, { message: "Source environment is required" }), + initialSyncBehavior: z.nativeEnum(IntegrationSyncBehavior), + secretPrefix: z.string().default(""), + useLabels: z.boolean().default(false), + azureLabel: z.string().min(1).optional() +}); + +type TFormSchema = z.infer; + +const initialSyncBehaviors = [ + { + label: "No Import - Overwrite all values in Azure App Configuration", + value: IntegrationSyncBehavior.OVERWRITE_TARGET + }, + { + label: "Import - Prefer values from Azure App Configuration", + value: IntegrationSyncBehavior.PREFER_TARGET + }, + { label: "Import - Prefer values from Infisical", value: IntegrationSyncBehavior.PREFER_SOURCE } +]; + +export const AzureAppConfigurationConfigurePage = () => { + const navigate = useNavigate(); + const { + control, + watch, + setValue, + handleSubmit, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + secretPath: "/", + secretPrefix: "", + initialSyncBehavior: IntegrationSyncBehavior.PREFER_SOURCE + } + }); + + const { mutateAsync } = useCreateIntegration(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.AzureAppConfigurationsConfigurePage.id, + select: (el) => el.integrationAuthId + }); + const { currentWorkspace } = useWorkspace(); + + const { data: workspace } = useGetWorkspaceById(currentWorkspace.id); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + + useEffect(() => { + if (workspace) { + setValue("sourceEnvironment", workspace.environments[0].slug); + } + }, [workspace]); + + const shouldUseLabels = watch("useLabels"); + + const handleIntegrationSubmit = async ({ + secretPath, + useLabels, + sourceEnvironment, + baseUrl, + initialSyncBehavior, + secretPrefix, + azureLabel + }: TFormSchema) => { + try { + if (!integrationAuth?.id) return; + + if (useLabels && !azureLabel) { + createNotification({ + type: "error", + text: "Label must be provided when 'Use Labels' is enabled" + }); + return; + } + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: baseUrl, + sourceEnvironment, + secretPath, + metadata: { + initialSyncBehavior, + secretPrefix, + ...(useLabels && { azureLabel }) + } + }); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && workspace ? ( +
+ + Set Up Azure App Configuration Integration + + + +
+
+ Azure logo +
+ Azure App Configuration + +
+ + Docs + +
+
+
+
+
+
+ ( + + + + )} + /> + +
+ ( + onChange(isChecked)} + isChecked={value} + > + + + )} + /> + + {shouldUseLabels && ( + ( + + + + )} + /> + )} +
+
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + +
+
+
+ ) : ( +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/route.tsx new file mode 100644 index 000000000..368db82e2 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { AzureAppConfigurationConfigurePage } from "./AzureAppConfigurationConfigurePage"; + +const AzureAppConfigurationPageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-app-configuration/create" +)({ + component: AzureAppConfigurationConfigurePage, + validateSearch: zodValidator(AzureAppConfigurationPageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/AzureAppConfigurationOauthCallbackPage.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/AzureAppConfigurationOauthCallbackPage.tsx new file mode 100644 index 000000000..87931f3b9 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/AzureAppConfigurationOauthCallbackPage.tsx @@ -0,0 +1,46 @@ +import { useEffect } from "react"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useAuthorizeIntegration } from "@app/hooks/api"; + +export const AzureAppConfigurationOauthCallbackPage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useAuthorizeIntegration(); + + const { code, state } = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.HerokuOauthCallbackPage.id + }); + const { currentWorkspace } = useWorkspace(); + + useEffect(() => { + (async () => { + try { + // validate state + if (state !== localStorage.getItem("latestCSRFToken")) return; + localStorage.removeItem("latestCSRFToken"); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + code: code as string, + integration: "azure-app-configuration" + }); + + navigate({ + to: "/secret-manager/$projectId/integrations/azure-app-configuration/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + })(); + }, []); + + return
; +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/route.tsx new file mode 100644 index 000000000..a52cffeac --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/route.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { AzureAppConfigurationOauthCallbackPage } from "./AzureAppConfigurationOauthCallbackPage"; + +export const AzureAppConfigurationOauthCallbackPageQueryParamsSchema = z.object({ + state: z.string().catch(""), + code: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-app-configuration/oauth2/callback" +)({ + component: AzureAppConfigurationOauthCallbackPage, + validateSearch: zodValidator(AzureAppConfigurationOauthCallbackPageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/AzureDevopsAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/AzureDevopsAuthorizePage.tsx new file mode 100644 index 000000000..5581cca4a --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/AzureDevopsAuthorizePage.tsx @@ -0,0 +1,124 @@ +import { useState } from "react"; +import { Helmet } from "react-helmet"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +export const AzureDevopsAuthorizePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const [apiKey, setApiKey] = useState(""); + const [devopsOrgName, setDevopsOrgName] = useState(""); + const [apiKeyErrorText, setApiKeyErrorText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const { currentWorkspace } = useWorkspace(); + + const handleButtonClick = async () => { + try { + setApiKeyErrorText(""); + if (apiKey.length === 0) { + setApiKeyErrorText("API Key cannot be blank"); + return; + } + + setIsLoading(true); + + localStorage.setItem("azure-devops-org-name", devopsOrgName); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "azure-devops", + accessToken: btoa(`:${apiKey}`) // This is a base64 encoding of the API key without any username + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations/azure-devops/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Authorize Azure DevOps Integration + + + +
+
+ Azure DevOps logo +
+ Azure DevOps Integration + +
+ + Docs + +
+
+
+
{" "} + + setApiKey(e.target.value)} /> + + + setDevopsOrgName(e.target.value)} + /> + + +
+
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/route.tsx new file mode 100644 index 000000000..13091e083 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AzureDevopsAuthorizePage } from "./AzureDevopsAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/authorize" +)({ + component: AzureDevopsAuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx new file mode 100644 index 000000000..82414b3af --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx @@ -0,0 +1,192 @@ +import { useEffect, useState } from "react"; +import { Helmet } from "react-helmet"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from "@app/hooks/api/integrationAuth"; +import { useGetWorkspaceById } from "@app/hooks/api/workspace"; + +export const AzureDevopsConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useCreateIntegration(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.AzureDevopsConfigurePage.id, + select: (el) => el.integrationAuthId + }); + const { currentWorkspace } = useWorkspace(); + + const { data: workspace } = useGetWorkspaceById(currentWorkspace.id); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "", + azureDevOpsOrgName: localStorage.getItem("azure-devops-org-name") ?? "" + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + const [targetApp, setTargetApp] = useState(""); + + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } + }, [workspace]); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + } else { + setTargetApp("none"); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?.id) return; + + setIsLoading(true); + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: localStorage.getItem("azure-devops-org-name") || "", + appId: targetApp, + sourceEnvironment: selectedSourceEnvironment, + secretPath + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && + workspace && + selectedSourceEnvironment && + integrationAuthApps && + targetApp ? ( +
+ + Set Up Azure DevOps Integration + + + +
+
+ Azure DevOps logo +
+ Azure DevOps Integration + +
+ + Docs + +
+
+
+
+ + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + +
+
+ ) : ( +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/route.tsx new file mode 100644 index 000000000..7585e904b --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { AzureDevopsConfigurePage } from "./AzureDevopsConfigurePage"; + +const AzureDevopsConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-devops/create" +)({ + component: AzureDevopsConfigurePage, + validateSearch: zodValidator(AzureDevopsConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx new file mode 100644 index 000000000..aaf65289a --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx @@ -0,0 +1,166 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; +import { useGetWorkspaceById } from "@app/hooks/api/workspace"; + +const initialSyncBehaviors = [ + { + label: "No Import - Overwrite all values in Azure Vault", + value: IntegrationSyncBehavior.OVERWRITE_TARGET + }, + { + label: "Import - Prefer values from Azure Vault", + value: IntegrationSyncBehavior.PREFER_TARGET + }, + { label: "Import - Prefer values from Infisical", value: IntegrationSyncBehavior.PREFER_SOURCE } +]; + +export const AzureKeyVaultConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useCreateIntegration(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.AzureKeyVaultConfigurePage.id, + select: (el) => el.integrationAuthId + }); + const { currentWorkspace } = useWorkspace(); + + const { data: workspace } = useGetWorkspaceById(currentWorkspace.id); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + + const [vaultBaseUrl, setVaultBaseUrl] = useState(""); + const [vaultBaseUrlErrorText, setVaultBaseUrlErrorText] = useState(""); + const [initialSyncBehavior, setInitialSyncBehavior] = useState( + IntegrationSyncBehavior.PREFER_SOURCE + ); + + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } + }, [workspace]); + + const handleButtonClick = async () => { + try { + if (vaultBaseUrl.length === 0) { + setVaultBaseUrlErrorText("Vault URI cannot be blank"); + return; + } + + if (!vaultBaseUrl.startsWith("https://") || !vaultBaseUrl.endsWith("vault.azure.net")) { + setVaultBaseUrlErrorText("Vault URI must be like https://.vault.azure.net"); + return; + } + + if (!integrationAuth?.id) return; + + setIsLoading(true); + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: vaultBaseUrl, + sourceEnvironment: selectedSourceEnvironment, + secretPath, + metadata: { + initialSyncBehavior + } + }); + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && workspace && selectedSourceEnvironment ? ( +
+ + Azure Key Vault Integration + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + setVaultBaseUrl(e.target.value)} + /> + + + + + + +
+ ) : ( +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/route.tsx new file mode 100644 index 000000000..5bcbfdebc --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { AzureKeyVaultConfigurePage } from "./AzureKeyVaultConfigurePage"; + +const AzureKeyVaultConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/create" +)({ + component: AzureKeyVaultConfigurePage, + validateSearch: zodValidator(AzureKeyVaultConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/AzureKeyVaultOauthCallback.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/AzureKeyVaultOauthCallback.tsx new file mode 100644 index 000000000..88c73aacb --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/AzureKeyVaultOauthCallback.tsx @@ -0,0 +1,46 @@ +import { useEffect } from "react"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useAuthorizeIntegration } from "@app/hooks/api"; + +export const AzureKeyVaultOauthCallbackPage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useAuthorizeIntegration(); + + const { code, state } = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.AzureKeyVaultOauthCallbackPage.id + }); + const { currentWorkspace } = useWorkspace(); + + useEffect(() => { + (async () => { + try { + // validate state + if (state !== localStorage.getItem("latestCSRFToken")) return; + localStorage.removeItem("latestCSRFToken"); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + code: code as string, + integration: "azure-key-vault" + }); + + navigate({ + to: "/secret-manager/$projectId/integrations/heroku/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + })(); + }, []); + + return
; +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/route.tsx new file mode 100644 index 000000000..e69a69e81 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/route.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { AzureKeyVaultOauthCallbackPage } from "./AzureKeyVaultOauthCallback"; + +export const AzureKeyVaultOauthCallbackQueryParamsSchema = z.object({ + state: z.string().catch(""), + code: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback" +)({ + component: AzureKeyVaultOauthCallbackPage, + validateSearch: zodValidator(AzureKeyVaultOauthCallbackQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx new file mode 100644 index 000000000..5d4d9710b --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx @@ -0,0 +1,307 @@ +import { useEffect } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { SiBitbucket } from "react-icons/si"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useNavigate, useSearch } from "@tanstack/react-router"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Card, + CardTitle, + FilterableSelect, + FormControl, + Input, + Spinner +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { + useCreateIntegration, + useGetIntegrationAuthApps, + useGetIntegrationAuthBitBucketWorkspaces +} from "@app/hooks/api"; +import { useGetIntegrationAuthBitBucketEnvironments } from "@app/hooks/api/integrationAuth/queries"; + +enum BitbucketScope { + Repo = "repo", + Env = "environment" +} + +const ScopeOptions = [ + { + label: "Repository", + value: BitbucketScope.Repo + }, + { + label: "Deployment Environment", + value: BitbucketScope.Env + } +]; + +const formSchema = z + .object({ + secretPath: z.string().default("/"), + sourceEnvironment: z.object({ name: z.string(), slug: z.string() }), + targetRepo: z.object({ name: z.string(), appId: z.string() }), + targetWorkspace: z.object({ name: z.string(), slug: z.string() }), + targetEnvironment: z.object({ name: z.string(), uuid: z.string() }).optional(), + scope: z.object({ label: z.string(), value: z.nativeEnum(BitbucketScope) }) + }) + .superRefine((val, ctx) => { + if (val.scope.value === BitbucketScope.Env && !val.targetEnvironment) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["targetEnvironment"], + message: "Required" + }); + } + }); + +type TFormData = z.infer; + +export const BitbucketConfigurePage = () => { + const navigate = useNavigate(); + const createIntegration = useCreateIntegration(); + + const { watch, control, reset, handleSubmit } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + secretPath: "/" + } + }); + + const bitBucketWorkspace = watch("targetWorkspace"); + const bitBucketRepo = watch("targetRepo"); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.BitbucketConfigurePage.id, + select: (el) => el.integrationAuthId + }); + const { currentWorkspace } = useWorkspace(); + + const { data: bitbucketWorkspaces, isPending: isBitbucketWorkspacesLoading } = + useGetIntegrationAuthBitBucketWorkspaces((integrationAuthId as string) ?? ""); + + const { data: bitbucketRepos, isPending: isBitbucketReposLoading } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "", + workspaceSlug: bitBucketWorkspace?.slug + }); + + const { data: bitbucketEnvironments } = useGetIntegrationAuthBitBucketEnvironments( + { + integrationAuthId: (integrationAuthId as string) ?? "", + workspaceSlug: bitBucketWorkspace?.slug, + repoSlug: bitBucketRepo?.appId + }, + { enabled: Boolean(bitBucketWorkspace?.slug && bitBucketRepo?.appId) } + ); + + const onSubmit = async ({ + targetRepo, + sourceEnvironment, + targetWorkspace, + secretPath, + targetEnvironment, + scope + }: TFormData) => { + try { + await createIntegration.mutateAsync({ + integrationAuthId, + isActive: true, + app: targetRepo.name, + appId: targetRepo.appId, + sourceEnvironment: sourceEnvironment.slug, + targetEnvironment: targetWorkspace.name, + targetEnvironmentId: targetWorkspace.slug, + ...(scope.value === BitbucketScope.Env && + targetEnvironment && { + targetService: targetEnvironment.name, + targetServiceId: targetEnvironment.uuid + }), + secretPath + }); + + createNotification({ + type: "success", + text: "Successfully created integration" + }); + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + createNotification({ + type: "error", + text: "Failed to create integration" + }); + console.error(err); + } + }; + + useEffect(() => { + if (!bitbucketRepos || !bitbucketWorkspaces || !currentWorkspace) return; + + reset({ + targetRepo: bitbucketRepos[0], + targetWorkspace: bitbucketWorkspaces[0], + sourceEnvironment: currentWorkspace.environments[0], + secretPath: "/", + scope: ScopeOptions[0] + }); + }, [bitbucketWorkspaces, bitbucketRepos, currentWorkspace]); + + if (isBitbucketWorkspacesLoading || isBitbucketReposLoading) + return ( +
+ +
+ ); + + const scope = watch("scope"); + + return ( +
+ + + + Bitbucket Integration + + ( + + option.slug} + value={value} + getOptionLabel={(option) => option.name} + onChange={onChange} + options={currentWorkspace?.environments} + placeholder="Select a project environment" + isDisabled={!currentWorkspace?.environments.length} + /> + + )} + /> + ( + + + + )} + /> + ( + + option.slug} + value={value} + getOptionLabel={(option) => option.name} + onChange={onChange} + options={bitbucketWorkspaces} + placeholder={ + bitbucketWorkspaces?.length ? "Select a workspace..." : "No workspaces found..." + } + isDisabled={!bitbucketWorkspaces?.length} + /> + + )} + /> + ( + + option.appId!} + value={value} + getOptionLabel={(option) => option.name} + onChange={onChange} + options={bitbucketRepos} + placeholder={ + bitbucketRepos?.length ? "Select a repository..." : "No repositories found..." + } + isDisabled={!bitbucketRepos?.length} + /> + + )} + /> + ( + + option.value} + getOptionLabel={(option) => option.label} + onChange={onChange} + options={ScopeOptions} + /> + + )} + /> + + {scope?.value === BitbucketScope.Env && ( + ( + + option.uuid} + value={value} + getOptionLabel={(option) => option.name} + onChange={onChange} + options={bitbucketEnvironments} + placeholder={ + bitbucketEnvironments?.length + ? "Select an environment..." + : "No environments found..." + } + isDisabled={!bitbucketEnvironments?.length} + /> + + )} + /> + )} + + +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/BitbucketConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/BitbucketConfigurePage/route.tsx new file mode 100644 index 000000000..514a915ef --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/BitbucketConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { BitbucketConfigurePage } from "./BitbucketConfigurePage"; + +const BitbucketConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/create" +)({ + component: BitbucketConfigurePage, + validateSearch: zodValidator(BitbucketConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/BitbucketOauthCallbackPage.tsx b/frontend-v2/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/BitbucketOauthCallbackPage.tsx new file mode 100644 index 000000000..1d0f8d3da --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/BitbucketOauthCallbackPage.tsx @@ -0,0 +1,45 @@ +import { useEffect } from "react"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useAuthorizeIntegration } from "@app/hooks/api"; + +export const BitbucketOauthCallbackPage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useAuthorizeIntegration(); + const { code, state } = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.BitbucketOauthCallbackPage.id + }); + const { currentWorkspace } = useWorkspace(); + + useEffect(() => { + (async () => { + try { + // validate state + if (state !== localStorage.getItem("latestCSRFToken")) return; + localStorage.removeItem("latestCSRFToken"); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + code: code as string, + integration: "bitbucket" + }); + + navigate({ + to: "/secret-manager/$projectId/integrations/heroku/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + })(); + }, []); + + return
; +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/route.tsx new file mode 100644 index 000000000..1c4ed2f53 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/route.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { BitbucketOauthCallbackPage } from "./BitbucketOauthCallbackPage"; + +export const BitbucketOauthCallbackQueryParamsSchema = z.object({ + state: z.string().catch(""), + code: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/bitbucket/oauth2/callback" +)({ + component: BitbucketOauthCallbackPage, + validateSearch: zodValidator(BitbucketOauthCallbackQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/ChecklyAuthorizePage/ChecklyAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/ChecklyAuthorizePage/ChecklyAuthorizePage.tsx new file mode 100644 index 000000000..b3ba43fbd --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/ChecklyAuthorizePage/ChecklyAuthorizePage.tsx @@ -0,0 +1,115 @@ +import { useState } from "react"; +import { Helmet } from "react-helmet"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +export const ChecklyAuthorizePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useSaveIntegrationAccessToken(); + const { currentWorkspace } = useWorkspace(); + + const [accessToken, setAccessToken] = useState(""); + const [accessTokenErrorText, setAccessTokenErrorText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const handleButtonClick = async () => { + try { + setAccessTokenErrorText(""); + if (accessToken.length === 0) { + setAccessTokenErrorText("Access token cannot be blank"); + return; + } + + setIsLoading(true); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "checkly", + accessToken + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations/checkly/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Authorize Checkly Integration + + + +
+
+ Checkly logo +
+ Checkly Integration + +
+ + Docs + +
+
+
+
+ + setAccessToken(e.target.value)} + /> + + +
+
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/ChecklyAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/ChecklyAuthorizePage/route.tsx new file mode 100644 index 000000000..680577ff9 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/ChecklyAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { ChecklyAuthorizePage } from "./ChecklyAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/checkly/authorize" +)({ + component: ChecklyAuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx new file mode 100644 index 000000000..99b182bfe --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx @@ -0,0 +1,312 @@ +import { useEffect, useState } from "react"; +import { Helmet } from "react-helmet"; +import { faArrowUpRightFromSquare, faBookOpen, faBugs } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate, useSearch } from "@tanstack/react-router"; +import { motion } from "framer-motion"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem, + Tab, + TabList, + TabPanel, + Tabs +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById, + useGetIntegrationAuthChecklyGroups +} from "@app/hooks/api/integrationAuth"; +import { useGetWorkspaceById } from "@app/hooks/api/workspace"; + +enum TabSections { + Connection = "connection", + Options = "options" +} + +export const ChecklyConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useCreateIntegration(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.ChecklyConfigurePage.id, + select: (el) => el.integrationAuthId + }); + const { currentWorkspace } = useWorkspace(); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + const [secretSuffix, setSecretSuffix] = useState(""); + + const [targetAppId, setTargetAppId] = useState(""); + const [targetGroupId, setTargetGroupId] = useState(""); + + const [isLoading, setIsLoading] = useState(false); + + const { data: workspace } = useGetWorkspaceById(currentWorkspace.id); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: integrationAuthApps, isPending: isIntegrationAuthAppsLoading } = + useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "" + }); + const { data: integrationAuthGroups, isPending: isintegrationAuthGroupsLoading } = + useGetIntegrationAuthChecklyGroups({ + integrationAuthId: (integrationAuthId as string) ?? "", + accountId: targetAppId + }); + + useEffect(() => { + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } + }, [workspace]); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetAppId(integrationAuthApps[0].appId as string); + } else { + setTargetAppId("none"); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?.id) return; + + setIsLoading(true); + + const targetApp = integrationAuthApps?.find( + (integrationAuthApp) => integrationAuthApp.appId === targetAppId + ); + const targetGroup = integrationAuthGroups?.find( + (group) => group.groupId === Number(targetGroupId) + ); + + if (!targetApp) return; + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: targetApp?.name, + appId: targetApp?.appId, + sourceEnvironment: selectedSourceEnvironment, + targetService: targetGroup?.name, + targetServiceId: targetGroup?.groupId ? String(targetGroup?.groupId) : undefined, + secretPath, + metadata: { + secretSuffix + } + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && + workspace && + selectedSourceEnvironment && + integrationAuthApps && + integrationAuthGroups && + targetAppId ? ( +
+ + Set Up Checkly Integration + + + +
+
+ Checkly logo +
+ Checkly Integration + +
+ + Docs + +
+
+
+
+ + +
+ Connection + Options +
+
+ + + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + + + + + + + + + setSecretSuffix(evt.target.value)} + placeholder="Provide a suffix for secret names, default is no suffix" + /> + + + +
+ +
+
+ ) : ( +
+ + Set Up Checkly Integration + + {isIntegrationAuthAppsLoading || isintegrationAuthGroupsLoading ? ( + infisical loading indicator + ) : ( +
+ +

+ Something went wrong. Please contact{" "} + + support@infisical.com + {" "} + if the issue persists. +

+
+ )} +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/ChecklyConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/ChecklyConfigurePage/route.tsx new file mode 100644 index 000000000..831a881a4 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/ChecklyConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { ChecklyConfigurePage } from "./ChecklyConfigurePage"; + +const ChecklyConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/checkly/create" +)({ + component: ChecklyConfigurePage, + validateSearch: zodValidator(ChecklyConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/CircleCIAuthorizePage/CircleCIAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/CircleCIAuthorizePage/CircleCIAuthorizePage.tsx new file mode 100644 index 000000000..5de27cf49 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CircleCIAuthorizePage/CircleCIAuthorizePage.tsx @@ -0,0 +1,108 @@ +import { useState } from "react"; +import { Helmet } from "react-helmet"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +export const CircleCIAuthorizePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const [apiKey, setApiKey] = useState(""); + const [apiKeyErrorText, setApiKeyErrorText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const { currentWorkspace } = useWorkspace(); + + const handleButtonClick = async () => { + try { + setApiKeyErrorText(""); + if (apiKey.length === 0) { + setApiKeyErrorText("API Key cannot be blank"); + return; + } + + setIsLoading(true); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "circleci", + accessToken: apiKey + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations/circleci/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Authorize CircleCI Integration + + + +
+
+ CircleCI logo +
+ CircleCI Integration + +
+ + Docs + +
+
+
+
+ + setApiKey(e.target.value)} /> + + +
+
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/CircleCIAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/CircleCIAuthorizePage/route.tsx new file mode 100644 index 000000000..1fd0cd266 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CircleCIAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { CircleCIAuthorizePage } from "./CircleCIAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/circleci/authorize" +)({ + component: CircleCIAuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx new file mode 100644 index 000000000..23737dc2c --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx @@ -0,0 +1,313 @@ +import { Controller, useForm } from "react-hook-form"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useNavigate, useSearch } from "@tanstack/react-router"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Card, + CardTitle, + FilterableSelect, + FormControl, + Select, + SelectItem, + Spinner +} from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration, useGetIntegrationAuthCircleCIOrganizations } from "@app/hooks/api"; +import { CircleCiScope } from "@app/hooks/api/integrationAuth/types"; + +const formSchema = z.discriminatedUnion("scope", [ + z.object({ + scope: z.literal(CircleCiScope.Context), + secretPath: z.string().default("/"), + sourceEnvironment: z.object({ name: z.string(), slug: z.string() }), + targetOrg: z.object({ name: z.string().min(1), slug: z.string().min(1) }), + targetContext: z.object({ name: z.string().min(1), id: z.string().min(1) }) + }), + z.object({ + scope: z.literal(CircleCiScope.Project), + secretPath: z.string().default("/"), + sourceEnvironment: z.object({ name: z.string(), slug: z.string() }), + targetOrg: z.object({ name: z.string().min(1), slug: z.string().min(1) }), + targetProject: z.object({ name: z.string().min(1), id: z.string().min(1) }) + }) +]); + +type TFormData = z.infer; + +export const CircleCIConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync, isPending: isCreatingIntegration } = useCreateIntegration(); + const { currentWorkspace } = useWorkspace(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.CircleConfigurePage.id, + select: (el) => el.integrationAuthId + }); + + const { control, watch, handleSubmit, setValue } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + secretPath: "/", + sourceEnvironment: currentWorkspace?.environments[0], + scope: CircleCiScope.Project + } + }); + + const selectedScope = watch("scope"); + const selectedOrg = watch("targetOrg"); + + const { data: circleCIOrganizations, isPending: isCircleCIOrganizationsLoading } = + useGetIntegrationAuthCircleCIOrganizations(integrationAuthId); + + const selectedOrganizationEntry = selectedOrg + ? circleCIOrganizations?.find((org) => org.slug === selectedOrg.slug) + : undefined; + + const onSubmit = async (data: TFormData) => { + try { + if (data.scope === CircleCiScope.Context) { + await mutateAsync({ + scope: data.scope, + integrationAuthId, + isActive: true, + sourceEnvironment: data.sourceEnvironment.slug, + app: data.targetContext.name, + appId: data.targetContext.id, + owner: data.targetOrg.name, + secretPath: data.secretPath + }); + } else { + await mutateAsync({ + scope: data.scope, + integrationAuthId, + isActive: true, + app: data.targetProject.name, // project name + owner: data.targetOrg.name, // organization name + appId: data.targetProject.id, // project id (used for syncing) + sourceEnvironment: data.sourceEnvironment.slug, + secretPath: data.secretPath + }); + } + + createNotification({ + type: "success", + text: "Successfully created integration" + }); + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + createNotification({ + type: "error", + text: "Failed to create integration" + }); + console.error(err); + } + }; + + if (isCircleCIOrganizationsLoading) + return ( +
+ +
+ ); + + return ( +
+ + +
+
+ CircleCI logo + + CircleCI Context Integration +
+ + +
+ + Docs + +
+
+
+
+ ( + + option.slug} + value={value} + getOptionLabel={(option) => option.name} + onChange={onChange} + options={currentWorkspace?.environments} + placeholder="Select a project environment" + isDisabled={!currentWorkspace?.environments.length} + /> + + )} + /> + ( + + + + )} + /> + ( + + option.slug} + value={value} + getOptionLabel={(option) => option.name} + onChange={(e) => { + setValue("targetProject", { + name: "", + id: "" + }); + setValue("targetContext", { + name: "", + id: "" + }); + + onChange(e); + }} + options={circleCIOrganizations} + placeholder={ + circleCIOrganizations?.length + ? "Select an organization..." + : "No organizations found..." + } + isDisabled={!circleCIOrganizations?.length} + /> + + )} + /> + ( + + + + )} + /> + {selectedScope === CircleCiScope.Context && selectedOrganizationEntry && ( + ( + + option.id!} + getOptionLabel={(option) => option.name} + onChange={onChange} + options={selectedOrganizationEntry?.contexts} + placeholder={ + selectedOrganizationEntry.contexts?.length + ? "Select a context..." + : "No contexts found..." + } + isDisabled={!selectedOrganizationEntry.contexts?.length} + /> + + )} + /> + )} + {selectedScope === CircleCiScope.Project && selectedOrganizationEntry && ( + ( + + option.id!} + getOptionLabel={(option) => option.name} + onChange={onChange} + options={selectedOrganizationEntry?.projects} + placeholder={ + selectedOrganizationEntry.projects?.length + ? "Select a project..." + : "No projects found..." + } + isDisabled={!selectedOrganizationEntry.projects?.length} + /> + + )} + /> + )} + +
+
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/CircleCIConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/CircleCIConfigurePage/route.tsx new file mode 100644 index 000000000..fd08b9870 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CircleCIConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { CircleCIConfigurePage } from "./CircleCIConfigurePage"; + +const CircleCIConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/circleci/create" +)({ + component: CircleCIConfigurePage, + validateSearch: zodValidator(CircleCIConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/Cloud66AuthorizePage/Cloud66AuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/Cloud66AuthorizePage/Cloud66AuthorizePage.tsx new file mode 100644 index 000000000..4af4ca60f --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/Cloud66AuthorizePage/Cloud66AuthorizePage.tsx @@ -0,0 +1,72 @@ +import { useState } from "react"; +import { useNavigate } from "@tanstack/react-router"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +export const Cloud66AuthorizePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const { currentWorkspace } = useWorkspace(); + + const [apiKey, setApiKey] = useState(""); + const [apiKeyErrorText, setApiKeyErrorText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const handleButtonClick = async () => { + try { + setApiKeyErrorText(""); + if (apiKey.length === 0) { + setApiKeyErrorText("Access token cannot be blank"); + return; + } + + setIsLoading(true); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "cloud-66", + accessToken: apiKey + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations/cloud-66/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Cloud 66 Integration + + setApiKey(e.target.value)} /> + + + +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/Cloud66AuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/Cloud66AuthorizePage/route.tsx new file mode 100644 index 000000000..d73b4ff33 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/Cloud66AuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { Cloud66AuthorizePage } from "./Cloud66AuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/cloud-66/authorize" +)({ + component: Cloud66AuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx new file mode 100644 index 000000000..73c410ce2 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx @@ -0,0 +1,151 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from "@app/hooks/api/integrationAuth"; + +export const Cloud66ConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useCreateIntegration(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.Cloud66ConfigurePage.id, + select: (el) => el.integrationAuthId + }); + + const { currentWorkspace } = useWorkspace(); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "" + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); + const [targetApp, setTargetApp] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (currentWorkspace) { + setSelectedSourceEnvironment(currentWorkspace.environments[0].slug); + } + }, [currentWorkspace]); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + } else { + setTargetApp("none"); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?.id) return; + + setIsLoading(true); + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: targetApp, + appId: integrationAuthApps?.find( + (integrationAuthApp) => integrationAuthApp.name === targetApp + )?.appId, + sourceEnvironment: selectedSourceEnvironment, + secretPath + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && selectedSourceEnvironment && integrationAuthApps && targetApp ? ( +
+ + Cloud 66 Integration + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + + +
+ ) : ( +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/Cloud66ConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/Cloud66ConfigurePage/route.tsx new file mode 100644 index 000000000..f3f3ec878 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/Cloud66ConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { Cloud66ConfigurePage } from "./Cloud66ConfigurePage"; + +const Cloud66ConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/cloud-66/create" +)({ + component: Cloud66ConfigurePage, + validateSearch: zodValidator(Cloud66ConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/CloudflarePagesAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/CloudflarePagesAuthorizePage.tsx new file mode 100644 index 000000000..58e7455be --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/CloudflarePagesAuthorizePage.tsx @@ -0,0 +1,101 @@ +import { useState } from "react"; +import { useNavigate } from "@tanstack/react-router"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +export const CloudflarePagesAuthorizePage = () => { + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const [accessKey, setAccessKey] = useState(""); + const [accessKeyErrorText, setAccessKeyErrorText] = useState(""); + const [accountId, setAccountId] = useState(""); + const [accountIdErrorText, setAccountIdErrorText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const { currentWorkspace } = useWorkspace(); + const navigate = useNavigate(); + + const handleButtonClick = async () => { + try { + setAccessKeyErrorText(""); + setAccountIdErrorText(""); + if (accessKey.length === 0 || accountId.length === 0) { + if (accessKey.length === 0) setAccessKeyErrorText("API token cannot be blank!"); + if (accountId.length === 0) setAccountIdErrorText("Account ID cannot be blank!"); + return; + } + + setIsLoading(true); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "cloudflare-pages", + accessId: accountId, + accessToken: accessKey + }); + + setAccessKey(""); + setAccountId(""); + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations/cloudflare-pages/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + + Cloudflare Pages Integration + + + setAccessKey(e.target.value)} + /> + + + setAccountId(e.target.value)} /> + + + +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/route.tsx new file mode 100644 index 000000000..bdc5ba5f4 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { CloudflarePagesAuthorizePage } from "./CloudflarePagesAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-pages/authorize" +)({ + component: CloudflarePagesAuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx new file mode 100644 index 000000000..1101d8081 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx @@ -0,0 +1,219 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useSearch } from "@tanstack/react-router"; +import axios from "axios"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Card, + CardTitle, + FormControl, + Select, + SelectItem, + Switch +} from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration, useGetWorkspaceById } from "@app/hooks/api"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from "@app/hooks/api/integrationAuth"; + +const cloudflareEnvironments = [ + { name: "Production", slug: "production" }, + { name: "Preview", slug: "preview" } +]; + +export const CloudflarePagesConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useCreateIntegration(); + + const { currentWorkspace } = useWorkspace(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.CloudflarePagesConfigurePage.id, + select: (el) => el.integrationAuthId + }); + + const [secretPath, setSecretPath] = useState("/"); + const { data: workspace } = useGetWorkspaceById(currentWorkspace.id); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "" + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); + const [targetApp, setTargetApp] = useState(""); + const [targetAppId, setTargetAppId] = useState(""); + const [targetEnvironment, setTargetEnvironment] = useState(""); + const [shouldAutoRedeploy, setShouldAutoRedeploy] = useState(false); + + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } + }, [workspace]); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + setTargetAppId(String(integrationAuthApps[0].appId)); + setTargetEnvironment(cloudflareEnvironments[0].slug); + } else { + setTargetApp("none"); + setTargetEnvironment(cloudflareEnvironments[0].slug); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?.id) return; + + setIsLoading(true); + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: targetApp, + appId: targetAppId, + sourceEnvironment: selectedSourceEnvironment, + targetEnvironment, + secretPath, + metadata: { + shouldAutoRedeploy + } + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + + let errorMessage: string = "Something went wrong!"; + if (axios.isAxiosError(err)) { + const { message } = err?.response?.data as { message: string }; + errorMessage = message; + } + + createNotification({ + text: errorMessage, + type: "error" + }); + setIsLoading(false); + } + }; + + return integrationAuth && + workspace && + selectedSourceEnvironment && + integrationAuthApps && + targetEnvironment && + targetApp ? ( +
+ + + Cloudflare Pages Integration + + + + + + setSecretPath(value)} + environment={selectedSourceEnvironment} + placeholder="Provide a path, default is /" + /> + + + + + + + +
+ setShouldAutoRedeploy(isChecked)} + isChecked={shouldAutoRedeploy} + > + Auto-redeploy service upon secret change + +
+ +
+
+ ) : ( +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/route.tsx new file mode 100644 index 000000000..6ec6d2b2d --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { CloudflarePagesConfigurePage } from "./CloudflarePagesConfigurePage"; + +const CloudflarePagesConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-pages/create" +)({ + component: CloudflarePagesConfigurePage, + validateSearch: zodValidator(CloudflarePagesConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/CloudflareWorkersAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/CloudflareWorkersAuthorizePage.tsx new file mode 100644 index 000000000..33b33f739 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/CloudflareWorkersAuthorizePage.tsx @@ -0,0 +1,101 @@ +import { useState } from "react"; +import { useNavigate } from "@tanstack/react-router"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +export const CloudflareWorkersAuthorizePage = () => { + const navigate = useNavigate(); + + const { currentWorkspace } = useWorkspace(); + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const [accessKey, setAccessKey] = useState(""); + const [accessKeyErrorText, setAccessKeyErrorText] = useState(""); + const [accountId, setAccountId] = useState(""); + const [accountIdErrorText, setAccountIdErrorText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const handleButtonClick = async () => { + try { + setAccessKeyErrorText(""); + setAccountIdErrorText(""); + if (accessKey.length === 0 || accountId.length === 0) { + if (accessKey.length === 0) setAccessKeyErrorText("API token cannot be blank!"); + if (accountId.length === 0) setAccountIdErrorText("Account ID cannot be blank!"); + return; + } + + setIsLoading(true); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "cloudflare-workers", + accessId: accountId, + accessToken: accessKey + }); + + setAccessKey(""); + setAccountId(""); + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations/cloudflare-workers/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + + Cloudflare Workers Integration + + + setAccessKey(e.target.value)} + /> + + + setAccountId(e.target.value)} /> + + + +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/route.tsx new file mode 100644 index 000000000..6365e26a3 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { CloudflareWorkersAuthorizePage } from "./CloudflareWorkersAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-workers/authorize" +)({ + component: CloudflareWorkersAuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx new file mode 100644 index 000000000..0f2149633 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx @@ -0,0 +1,162 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useSearch } from "@tanstack/react-router"; +import axios from "axios"; + +import { createNotification } from "@app/components/notifications"; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from "@app/hooks/api/integrationAuth"; + +export const CloudflareWorkersConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useCreateIntegration(); + const { currentWorkspace } = useWorkspace(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.CloudflareWorkersConfigurePage.id, + select: (el) => el.integrationAuthId + }); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "" + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( + currentWorkspace.environments[0].slug + ); + const [secretPath, setSecretPath] = useState("/"); + + const [targetApp, setTargetApp] = useState(""); + const [targetAppId, setTargetAppId] = useState(""); + + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + setTargetAppId(String(integrationAuthApps[0].appId)); + } else { + setTargetApp("none"); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?.id) return; + + setIsLoading(true); + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: targetApp, + appId: targetAppId, + sourceEnvironment: selectedSourceEnvironment, + secretPath + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + + let errorMessage: string = "Something went wrong!"; + if (axios.isAxiosError(err)) { + const { message } = err?.response?.data as { message: string }; + errorMessage = message; + } + + createNotification({ + text: errorMessage, + type: "error" + }); + setIsLoading(false); + } + }; + + return integrationAuth && selectedSourceEnvironment && integrationAuthApps && targetApp ? ( +
+ + + Cloudflare Workers Integration + + + + + + setSecretPath(value)} + environment={selectedSourceEnvironment} + placeholder="Provide a path, default is /" + /> + + + + + + +
+ ) : ( +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/route.tsx new file mode 100644 index 000000000..597000dec --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { CloudflareWorkersConfigurePage } from "./CloudflareWorkersConfigurePage"; + +const CloudflareWorkersConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/cloudflare-workers/create" +)({ + component: CloudflareWorkersConfigurePage, + validateSearch: zodValidator(CloudflareWorkersConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/CodefreshAuthorizePage/CodefreshAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/CodefreshAuthorizePage/CodefreshAuthorizePage.tsx new file mode 100644 index 000000000..622b71f83 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CodefreshAuthorizePage/CodefreshAuthorizePage.tsx @@ -0,0 +1,71 @@ +import { useState } from "react"; +import { useNavigate } from "@tanstack/react-router"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +export const CodefreshAuthorizePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const [apiKey, setApiKey] = useState(""); + const [apiKeyErrorText, setApiKeyErrorText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const { currentWorkspace } = useWorkspace(); + + const handleButtonClick = async () => { + try { + setApiKeyErrorText(""); + if (apiKey.length === 0) { + setApiKeyErrorText("API Key cannot be blank"); + return; + } + + setIsLoading(true); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "codefresh", + accessToken: apiKey + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations/codefresh/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Codefresh Integration + + setApiKey(e.target.value)} /> + + + +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/CodefreshAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/CodefreshAuthorizePage/route.tsx new file mode 100644 index 000000000..42e426306 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CodefreshAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { CodefreshAuthorizePage } from "./CodefreshAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/codefresh/authorize" +)({ + component: CodefreshAuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx new file mode 100644 index 000000000..1961236d9 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx @@ -0,0 +1,147 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from "@app/hooks/api/integrationAuth"; + +export const CodefreshConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useCreateIntegration(); + + const { currentWorkspace } = useWorkspace(); + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.CodefreshConfigurePage.id, + select: (el) => el.integrationAuthId + }); + + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "" + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( + currentWorkspace.environments[0].slug + ); + const [targetApp, setTargetApp] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + } else { + setTargetApp("none"); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?.id) return; + + setIsLoading(true); + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: targetApp, + appId: integrationAuthApps?.find( + (integrationAuthApp) => integrationAuthApp.name === targetApp + )?.appId, + sourceEnvironment: selectedSourceEnvironment, + secretPath + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && selectedSourceEnvironment && integrationAuthApps && targetApp ? ( +
+ + Codefresh Integration + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + + +
+ ) : ( +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/CodefreshConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/CodefreshConfigurePage/route.tsx new file mode 100644 index 000000000..bae29183c --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/CodefreshConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { CodefreshConfigurePage } from "./CodefreshConfigurePage"; + +const CodefreshConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/codefresh/create" +)({ + component: CodefreshConfigurePage, + validateSearch: zodValidator(CodefreshConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/DatabricksAuthorizePage/DatabricksAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/DatabricksAuthorizePage/DatabricksAuthorizePage.tsx new file mode 100644 index 000000000..920518b24 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/DatabricksAuthorizePage/DatabricksAuthorizePage.tsx @@ -0,0 +1,130 @@ +import { useState } from "react"; +import { Helmet } from "react-helmet"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +export const DatabricksAuthorizePage = () => { + const navigate = useNavigate(); + const { mutateAsync, isPending } = useSaveIntegrationAccessToken(); + + const [apiKey, setApiKey] = useState(""); + const [instanceURL, setInstanceURL] = useState(""); + const [apiKeyErrorText, setApiKeyErrorText] = useState(""); + const [instanceURLErrorText, setInstanceURLErrorText] = useState(""); + + const { currentWorkspace } = useWorkspace(); + + const handleButtonClick = async () => { + try { + setApiKeyErrorText(""); + setInstanceURLErrorText(""); + if (apiKey.length === 0) { + setApiKeyErrorText("API Key cannot be blank"); + return; + } + if (instanceURL.length === 0) { + setInstanceURLErrorText("Instance URL cannot be blank"); + return; + } + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "databricks", + url: instanceURL.replace(/\/$/, ""), + accessToken: apiKey + }); + + navigate({ + to: "/secret-manager/$projectId/integrations/databricks/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Authorize Databricks Integration + + + +
+
+ Databricks logo +
+ Databricks Integration + +
+ + Docs + +
+
+
+
+ + setInstanceURL(e.target.value)} + placeholder="https://xxxx.cloud.databricks.com" + /> + + + setApiKey(e.target.value)} + type="password" + /> + + +
+
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/DatabricksAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/DatabricksAuthorizePage/route.tsx new file mode 100644 index 000000000..f1fc79379 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/DatabricksAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { DatabricksAuthorizePage } from "./DatabricksAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/databricks/authorize" +)({ + component: DatabricksAuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx new file mode 100644 index 000000000..be605cd58 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx @@ -0,0 +1,241 @@ +import { useState } from "react"; +import { Helmet } from "react-helmet"; +import { + faArrowUpRightFromSquare, + faBookOpen, + faBugs, + faCircleInfo +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from "@app/hooks/api/integrationAuth"; + +export const DatabricksConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync, isPending } = useCreateIntegration(); + + const { currentWorkspace } = useWorkspace(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.DatabricksConfigurePage.id, + select: (el) => el.integrationAuthId + }); + + const { data: integrationAuth, isLoading: isintegrationAuthLoading } = useGetIntegrationAuthById( + (integrationAuthId as string) ?? "" + ); + + const { data: integrationAuthScopes, isLoading: isIntegrationAuthScopesLoading } = + useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "" + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( + currentWorkspace.environments[0].slug + ); + const [targetScope, setTargetScope] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?.id) return; + + if (!targetScope) { + createNotification({ + type: "error", + text: "Please select a scope" + }); + return; + } + + const selectedScope = integrationAuthScopes?.find( + (integrationAuthScope) => integrationAuthScope.name === targetScope + ); + + if (!selectedScope) { + createNotification({ + type: "error", + text: "Invalid scope selected" + }); + return; + } + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: selectedScope.name, // scope name + sourceEnvironment: selectedSourceEnvironment, + secretPath + }); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && selectedSourceEnvironment && integrationAuthScopes ? ( +
+ + Set Up Databricks Integration + + + +
+
+ Databricks logo +
+ Databricks Integration + +
+ + Docs + +
+
+
+
+ + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + {integrationAuthScopes && ( + + + + )} + +
+
+
+
+ {" "} + Pro Tip +
+ + After creating an integration, your secrets will start syncing immediately. This might + cause an unexpected override of current secrets in Databricks with secrets from Infisical. + +
+
+ ) : ( +
+ + Set Up Databricks Integration + + {isIntegrationAuthScopesLoading || isintegrationAuthLoading ? ( + infisical loading indicator + ) : ( +
+ +

+ Something went wrong. Please contact{" "} + + support@infisical.com + {" "} + if the issue persists. +

+
+ )} +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/DatabricksConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/DatabricksConfigurePage/route.tsx new file mode 100644 index 000000000..971678824 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/DatabricksConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { DatabricksConfigurePage } from "./DatabricksConfigurePage"; + +const DatabricksConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/databricks/create" +)({ + component: DatabricksConfigurePage, + validateSearch: zodValidator(DatabricksConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/DigitalOceanAppPlatformAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/DigitalOceanAppPlatformAuthorizePage.tsx new file mode 100644 index 000000000..be2483188 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/DigitalOceanAppPlatformAuthorizePage.tsx @@ -0,0 +1,71 @@ +import { useState } from "react"; +import { useNavigate } from "@tanstack/react-router"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +export const DigitalOceanAppPlatformAuthorizePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const [apiKey, setApiKey] = useState(""); + const [apiKeyErrorText, setApiKeyErrorText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const { currentWorkspace } = useWorkspace(); + + const handleButtonClick = async () => { + try { + setApiKeyErrorText(""); + if (apiKey.length === 0) { + setApiKeyErrorText("API Key cannot be blank"); + return; + } + + setIsLoading(true); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "digital-ocean-app-platform", + accessToken: apiKey + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations/digital-ocean-app-platform/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Digital Ocean App Platform Integration + + setApiKey(e.target.value)} /> + + + +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/route.tsx new file mode 100644 index 000000000..a7f41e997 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { DigitalOceanAppPlatformAuthorizePage } from "./DigitalOceanAppPlatformAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/digital-ocean-app-platform/authorize" +)({ + component: DigitalOceanAppPlatformAuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx new file mode 100644 index 000000000..61c2c5789 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx @@ -0,0 +1,148 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from "@app/hooks/api/integrationAuth"; + +export const DigitalOceanAppPlatformConfigurePage = () => { + const navigate = useNavigate(); + const { mutateAsync } = useCreateIntegration(); + + const { currentWorkspace } = useWorkspace(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.DigitalOceanAppPlatformConfigurePage.id, + select: (el) => el.integrationAuthId + }); + + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "" + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( + currentWorkspace.environments[0].slug + ); + const [targetApp, setTargetApp] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + } else { + setTargetApp("none"); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?.id) return; + + setIsLoading(true); + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: targetApp, + appId: integrationAuthApps?.find( + (integrationAuthApp) => integrationAuthApp.name === targetApp + )?.appId, + sourceEnvironment: selectedSourceEnvironment, + secretPath + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && selectedSourceEnvironment && integrationAuthApps && targetApp ? ( +
+ + Digital Ocean App Platform Integration + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + + +
+ ) : ( +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/route.tsx new file mode 100644 index 000000000..fb4e5edfa --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { DigitalOceanAppPlatformConfigurePage } from "./DigitalOceanAppPlatformConfigurePage"; + +const DigitalOceanAppPlatformConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/digital-ocean-app-platform/create" +)({ + component: DigitalOceanAppPlatformConfigurePage, + validateSearch: zodValidator(DigitalOceanAppPlatformConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/FlyioAuthorizePage/FlyioAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/FlyioAuthorizePage/FlyioAuthorizePage.tsx new file mode 100644 index 000000000..e9f43374f --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/FlyioAuthorizePage/FlyioAuthorizePage.tsx @@ -0,0 +1,120 @@ +import { useState } from "react"; +import { Helmet } from "react-helmet"; +import { Controller, useForm } from "react-hook-form"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useNavigate } from "@tanstack/react-router"; +import { z } from "zod"; + +import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useSaveIntegrationAccessToken } from "@app/hooks/api"; + +const schema = z.object({ + accessToken: z.string().trim() +}); + +type FormData = z.infer; + +export const FlyioAuthorizePage = () => { + const navigate = useNavigate(); + const { currentWorkspace } = useWorkspace(); + + const { control, handleSubmit } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + accessToken: "" + } + }); + + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const [isLoading, setIsLoading] = useState(false); + + const onFormSubmit = async ({ accessToken }: FormData) => { + try { + setIsLoading(true); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "flyio", + accessToken + }); + + setIsLoading(false); + navigate({ + to: "/secret-manager/$projectId/integrations/flyio/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + setIsLoading(false); + console.error(err); + } + }; + + return ( +
+ + Authorize Fly.io Integration + + + +
+
+ Fly.io logo +
+ Fly.io Integration + +
+ + Docs + +
+
+
+
+
+ ( + + + + )} + /> + + +
+
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/FlyioAuthorizePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/FlyioAuthorizePage/route.tsx new file mode 100644 index 000000000..752bd02e2 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/FlyioAuthorizePage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { FlyioAuthorizePage } from "./FlyioAuthorizePage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/flyio/authorize" +)({ + component: FlyioAuthorizePage +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx new file mode 100644 index 000000000..6b08409f5 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx @@ -0,0 +1,237 @@ +import { useEffect, useState } from "react"; +import { Helmet } from "react-helmet"; +import { + faArrowUpRightFromSquare, + faBookOpen, + faBugs, + faCircleInfo +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { + Alert, + AlertDescription, + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useWorkspace } from "@app/context"; +import { useCreateIntegration } from "@app/hooks/api"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from "@app/hooks/api/integrationAuth"; + +export const FlyioConfigurePage = () => { + const navigate = useNavigate(); + + const { mutateAsync } = useCreateIntegration(); + + const { currentWorkspace } = useWorkspace(); + + const integrationAuthId = useSearch({ + from: ROUTE_PATHS.SecretManager.Integratons.FlyioConfigurePage.id, + select: (el) => el.integrationAuthId + }); + + const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById( + (integrationAuthId as string) ?? "" + ); + const { data: integrationAuthApps = [], isLoading: isIntegrationAuthAppsLoading } = + useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "" + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState( + currentWorkspace.environments[0].slug + ); + const [secretPath, setSecretPath] = useState("/"); + + const [targetApp, setTargetApp] = useState(""); + + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + // TODO: handle case where apps can be empty + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + } else { + setTargetApp("none"); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?.id) return; + + setIsLoading(true); + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: targetApp, + sourceEnvironment: selectedSourceEnvironment, + secretPath + }); + + setIsLoading(false); + + navigate({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId: currentWorkspace.id + } + }); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && selectedSourceEnvironment && integrationAuthApps && targetApp ? ( +
+ + Set Up Fly.io Integration + + + +
+
+ Fly.io logo +
+ Fly.io Integration + +
+ + Docs + +
+
+
+
+
+ + + All current secrets linked to the related Fly.io project will be deleted before + Infisical secrets are pushed to your Fly.io project. + + +
+ + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + +
+
+
+
+ {" "} + Pro Tip +
+ + After creating an integration, your secrets will start syncing immediately. This might + cause an unexpected override of current secrets in Fly.io with secrets from Infisical. + +
+
+ ) : ( +
+ + Set Up Fly.io Integration + + {isIntegrationAuthLoading || isIntegrationAuthAppsLoading ? ( + infisical loading indicator + ) : ( +
+ +

+ Something went wrong. Please contact{" "} + + support@infisical.com + {" "} + if the issue persists. +

+
+ )} +
+ ); +}; diff --git a/frontend-v2/src/pages/secret-manager/integrations/FlyioConfigurePage/route.tsx b/frontend-v2/src/pages/secret-manager/integrations/FlyioConfigurePage/route.tsx new file mode 100644 index 000000000..9aa21ef16 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/FlyioConfigurePage/route.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; + +import { FlyioConfigurePage } from "./FlyioConfigurePage"; + +const FlyioConfigurePageQueryParamsSchema = z.object({ + integrationAuthId: z.string() +}); + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/flyio/create" +)({ + component: FlyioConfigurePage, + validateSearch: zodValidator(FlyioConfigurePageQueryParamsSchema) +}); diff --git a/frontend-v2/src/pages/secret-manager/integrations/GcpSecretManagerAuthorizePage/GcpSecretManagerAuthorizePage.tsx b/frontend-v2/src/pages/secret-manager/integrations/GcpSecretManagerAuthorizePage/GcpSecretManagerAuthorizePage.tsx new file mode 100644 index 000000000..7f0030706 --- /dev/null +++ b/frontend-v2/src/pages/secret-manager/integrations/GcpSecretManagerAuthorizePage/GcpSecretManagerAuthorizePage.tsx @@ -0,0 +1,170 @@ +import crypto from "crypto"; + +import { useState } from "react"; +import { Helmet } from "react-helmet"; +import { Controller, useForm } from "react-hook-form"; +import { faGoogle } from "@fortawesome/free-brands-svg-icons"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useNavigate } from "@tanstack/react-router"; +import { z } from "zod"; + +import { Button, Card, CardTitle, FormControl, TextArea } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { localStorageService } from "@app/helpers/localStorage"; +import { useGetCloudIntegrations, useSaveIntegrationAccessToken } from "@app/hooks/api"; + +import { createIntegrationMissingEnvVarsNotification } from "../../IntegrationsListPage/IntegrationsListPage.utils"; + +const schema = z.object({ + accessToken: z.string() +}); + +type FormData = z.infer; + +export const GcpSecretManagerAuthorizePage = () => { + const navigate = useNavigate(); + + const { control, handleSubmit } = useForm({ + resolver: zodResolver(schema) + }); + + const { data: cloudIntegrations } = useGetCloudIntegrations(); + const { currentWorkspace } = useWorkspace(); + + const { mutateAsync } = useSaveIntegrationAccessToken(); + + const [isLoading, setIsLoading] = useState(false); + + const handleIntegrateWithOAuth = () => { + if (!cloudIntegrations) return; + const integrationOption = cloudIntegrations.find( + (integration) => integration.slug === "gcp-secret-manager" + ); + + if (!integrationOption) return; + + const state = crypto.randomBytes(16).toString("hex"); + localStorage.setItem("latestCSRFToken", state); + localStorageService.setIntegrationProjectId(currentWorkspace.id); + + if (!integrationOption.clientId) { + createIntegrationMissingEnvVarsNotification(integrationOption.slug); + return; + } + + const link = `https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/cloud-platform&response_type=code&access_type=offline&state=${state}&redirect_uri=${window.location.origin}/integrations/gcp-secret-manager/oauth2/callback&client_id=${integrationOption.clientId}`; + window.location.assign(link); + }; + + const onFormSubmit = async ({ accessToken }: FormData) => { + try { + setIsLoading(true); + + const integrationAuth = await mutateAsync({ + workspaceId: currentWorkspace.id, + integration: "gcp-secret-manager", + refreshToken: accessToken + }); + + setIsLoading(false); + navigate({ + to: "/secret-manager/$projectId/integrations/gcp-secret-manager/create", + params: { + projectId: currentWorkspace.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); + } catch (err) { + console.error(err); + setIsLoading(false); + } + }; + + return ( +
+ + Authorize GCP Secret Manager Integration + + + +
+
+ GCP logo +
+ GCP Secret Manager Integration + +
+ + Docs + +
+
+
+
+
+ +
+
+ or +
+
+
+
+ ( + +