From 58a44ba42ecfc989bb04a665e71e7e69333d9d83 Mon Sep 17 00:00:00 2001 From: Nimish <85357445+nimish-ks@users.noreply.github.com> Date: Tue, 12 Nov 2024 19:10:49 +0530 Subject: [PATCH] Feat: vercel integration (#388) * feat: added vercel provider * feat: added vercel service config * feat: added vercel syncing util * chore: credential provider migration * chore: environment sync migration * feat: added vercel sync task trigger * feat: added sync job * feat: added vercel in schema.py * feat: added vercel project resolver * feat: vercel resource input * feat: added vercel sync graphene mutations * feat: added railway and vercel docs * fix: add a test vercel creds resolver * feat: graphql types * feat: graphql apollo types * feat: graphql schema * feat: added vercel create sync diaglog * feat: vercel provider icon * feat: create vercel sync graphql query mutation * feat: get all vercel projects graphql query * feat: added vercel in service info * feat: added create vercel sync component * chore: bumped version * fix: increase syncs menu width * Update frontend/components/syncing/Vercel/CreateVercelSync.tsx Co-authored-by: Rohan Chaturvedi * Update frontend/components/syncing/Vercel/CreateVercelSync.tsx Co-authored-by: Rohan Chaturvedi * fix: remove test vercel creds resolver * fix: removed unused imports * feat: updated graphql types * feat: removed test vercel graphql types --------- Co-authored-by: Rohan --- ...0085_alter_providercredentials_provider.py | 18 + .../0086_alter_environmentsync_service.py | 18 + backend/api/services.py | 15 + backend/api/tasks.py | 94 +++++ backend/api/utils/syncing/vercel/main.py | 168 +++++++++ backend/backend/graphene/mutations/syncing.py | 64 ++++ backend/backend/graphene/queries/syncing.py | 16 + backend/backend/schema.py | 13 + backend/version.txt | 2 +- frontend/apollo/gql.ts | 10 + frontend/apollo/graphql.ts | 70 +++- frontend/apollo/schema.graphql | 15 +- .../syncing/CreateProviderCredentials.tsx | 4 + .../components/syncing/CreateSyncDialog.tsx | 3 + frontend/components/syncing/EnvSyncStatus.tsx | 4 +- frontend/components/syncing/ProviderIcon.tsx | 4 + frontend/components/syncing/ServiceInfo.tsx | 14 +- .../syncing/Vercel/CreateVercelSync.tsx | 343 ++++++++++++++++++ .../syncing/vercel/createVercelSync.gql | 35 ++ .../queries/syncing/vercel/getProject.gql | 7 + 20 files changed, 906 insertions(+), 11 deletions(-) create mode 100644 backend/api/migrations/0085_alter_providercredentials_provider.py create mode 100644 backend/api/migrations/0086_alter_environmentsync_service.py create mode 100644 backend/api/utils/syncing/vercel/main.py create mode 100644 frontend/components/syncing/Vercel/CreateVercelSync.tsx create mode 100644 frontend/graphql/mutations/syncing/vercel/createVercelSync.gql create mode 100644 frontend/graphql/queries/syncing/vercel/getProject.gql diff --git a/backend/api/migrations/0085_alter_providercredentials_provider.py b/backend/api/migrations/0085_alter_providercredentials_provider.py new file mode 100644 index 000000000..b7102d62e --- /dev/null +++ b/backend/api/migrations/0085_alter_providercredentials_provider.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.15 on 2024-11-11 14:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0084_auto_20241008_0708'), + ] + + operations = [ + migrations.AlterField( + model_name='providercredentials', + name='provider', + field=models.CharField(choices=[('cloudflare', 'Cloudflare'), ('aws', 'AWS'), ('github', 'GitHub'), ('gitlab', 'GitLab'), ('hashicorp_vault', 'Hashicorp Vault'), ('hashicorp_nomad', 'Hashicorp Nomad'), ('railway', 'Railway'), ('vercel', 'Vercel')], max_length=50), + ), + ] diff --git a/backend/api/migrations/0086_alter_environmentsync_service.py b/backend/api/migrations/0086_alter_environmentsync_service.py new file mode 100644 index 000000000..c0f69cdc0 --- /dev/null +++ b/backend/api/migrations/0086_alter_environmentsync_service.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.15 on 2024-11-11 14:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0085_alter_providercredentials_provider'), + ] + + operations = [ + migrations.AlterField( + model_name='environmentsync', + name='service', + field=models.CharField(choices=[('cloudflare_pages', 'Cloudflare Pages'), ('aws_secrets_manager', 'AWS Secrets Manager'), ('github_actions', 'GitHub Actions'), ('gitlab_ci', 'GitLab CI'), ('hashicorp_vault', 'Hashicorp Vault'), ('hashicorp_nomad', 'Hashicorp Nomad'), ('railway', 'Railway'), ('vercel', 'Vercel')], max_length=50), + ), + ] diff --git a/backend/api/services.py b/backend/api/services.py index 5d388a1a5..110d6010f 100644 --- a/backend/api/services.py +++ b/backend/api/services.py @@ -62,6 +62,14 @@ class Providers: "auth_scheme": "token", } + VERCEL = { + "id": "vercel", + "name": "Vercel", + "expected_credentials": ["api_token"], + "optional_credentials": [], + "auth_scheme": "token", + } + @classmethod def get_provider_choices(cls): return [ @@ -134,6 +142,13 @@ class ServiceConfig: "resource_type": "environment", } + VERCEL = { + "id": "vercel", + "name": "Vercel", + "provider": Providers.VERCEL, + "resource_type": "environment", + } + @classmethod def get_service_choices(cls): return [ diff --git a/backend/api/tasks.py b/backend/api/tasks.py index b1c82e5e4..cd08841b6 100644 --- a/backend/api/tasks.py +++ b/backend/api/tasks.py @@ -10,6 +10,7 @@ from api.utils.syncing.nomad.main import sync_nomad_secrets from api.utils.syncing.gitlab.main import sync_gitlab_secrets from api.utils.syncing.railway.main import sync_railway_secrets +from api.utils.syncing.vercel.main import sync_vercel_secrets from .utils.syncing.cloudflare.pages import ( get_cf_pages_credentials, sync_cloudflare_secrets, @@ -92,6 +93,15 @@ def trigger_sync_tasks(env_sync): job_id = job.get_id() EnvironmentSyncEvent.objects.create(id=job_id, env_sync=env_sync) + + elif env_sync.service == ServiceConfig.VERCEL["id"]: + env_sync.status = EnvironmentSync.IN_PROGRESS + env_sync.save() + + job = perform_vercel_sync.delay(env_sync) + job_id = job.get_id() + + EnvironmentSyncEvent.objects.create(id=job_id, env_sync=env_sync) # try and cancel running or queued jobs for this sync @@ -760,3 +770,87 @@ def perform_railway_sync(environment_sync): environment_sync.last_sync = timezone.now() environment_sync.status = EnvironmentSync.FAILED environment_sync.save() + + +@job("default", timeout=3600) +def perform_vercel_sync(environment_sync): + try: + EnvironmentSync = apps.get_model("api", "EnvironmentSync") + EnvironmentSyncEvent = apps.get_model("api", "EnvironmentSyncEvent") + + secrets = get_environment_secrets( + environment_sync.environment, environment_sync.path + ) + + if environment_sync.authentication is None: + sync_data = ( + False, + {"message": "No authentication credentials for this sync"}, + ) + raise Exception("No authentication credentials for this sync") + + vercel_sync_options = environment_sync.options + + vercel_project = vercel_sync_options.get("project") + vercel_environment = vercel_sync_options.get("environment", "production") + vercel_secret_type = vercel_sync_options.get("secret_type", "encrypted") + + success, sync_data = sync_vercel_secrets( + secrets, + environment_sync.authentication.id, + vercel_project["id"], + vercel_environment, + vercel_secret_type, + ) + + sync_event = ( + EnvironmentSyncEvent.objects.filter(env_sync=environment_sync) + .order_by("-created_at") + .first() + ) + + if success: + sync_event.status = EnvironmentSync.COMPLETED + sync_event.completed_at = timezone.now() + sync_event.meta = sync_data + sync_event.save() + + environment_sync.last_sync = timezone.now() + environment_sync.status = EnvironmentSync.COMPLETED + environment_sync.save() + + else: + sync_event.status = EnvironmentSync.FAILED + sync_event.completed_at = timezone.now() + sync_event.meta = sync_data + sync_event.save() + + environment_sync.last_sync = timezone.now() + environment_sync.status = EnvironmentSync.FAILED + environment_sync.save() + + except JobTimeoutException: + # Handle timeout exception + sync_event.status = EnvironmentSync.TIMED_OUT + sync_event.completed_at = timezone.now() + sync_event.save() + + environment_sync.last_sync = timezone.now() + environment_sync.status = EnvironmentSync.TIMED_OUT + environment_sync.save() + raise # Re-raise the JobTimeoutException + + except Exception as ex: + print(f"EXCEPTION {ex}") + sync_event.status = EnvironmentSync.FAILED + sync_event.completed_at = timezone.now() + + try: + sync_event.meta = sync_data + except: + pass + sync_event.save() + + environment_sync.last_sync = timezone.now() + environment_sync.status = EnvironmentSync.FAILED + environment_sync.save() \ No newline at end of file diff --git a/backend/api/utils/syncing/vercel/main.py b/backend/api/utils/syncing/vercel/main.py new file mode 100644 index 000000000..cb69f7a9c --- /dev/null +++ b/backend/api/utils/syncing/vercel/main.py @@ -0,0 +1,168 @@ +import requests +import graphene +from graphene import ObjectType + +from api.utils.syncing.auth import get_credentials + +VERCEL_API_BASE_URL = 'https://api.vercel.com' + +class VercelProjectType(ObjectType): + id = graphene.ID(required=True) + name = graphene.String(required=True) + environment = graphene.List(graphene.String) + + +def get_vercel_credentials(credential_id): + """Get Vercel credentials from the encrypted storage.""" + credentials = get_credentials(credential_id) + token = credentials.get("api_token") + return token + + +def get_vercel_headers(token): + """Prepare headers for Vercel API requests.""" + return {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'} + + +def test_vercel_creds(credential_id): + """Test if the Vercel credentials are valid.""" + try: + token = get_vercel_credentials(credential_id) + url = f'{VERCEL_API_BASE_URL}/v2/user' + response = requests.get(url, headers=get_vercel_headers(token)) + return response.status_code == 200 + except Exception: + return False + + +def list_vercel_projects(credential_id): + """ + List all Vercel projects accessible with the provided credentials. + Returns a list of projects with their IDs, names, and available environments. + """ + try: + token = get_vercel_credentials(credential_id) + url = f'{VERCEL_API_BASE_URL}/v9/projects' + response = requests.get(url, headers=get_vercel_headers(token)) + + if response.status_code != 200: + raise Exception(f"Failed to list Vercel projects: {response.text}") + + projects = response.json().get('projects', []) + return [ + { + "id": project["id"], + "name": project["name"], + "environment": ["development", "preview", "production"] + } + for project in projects + ] + except Exception as e: + raise Exception(f"Error listing Vercel projects: {str(e)}") + + +def get_existing_env_vars(token, project_id): + """Retrieve all environment variables for a specific Vercel project.""" + url = f'{VERCEL_API_BASE_URL}/v9/projects/{project_id}/env' + response = requests.get(url, headers=get_vercel_headers(token)) + + if response.status_code != 200: + raise Exception(f"Error retrieving environment variables: {response.text}") + + return { + env['key']: { + 'id': env['id'], + 'value': env['value'], + 'target': env['target'], + 'comment': env.get('comment'), + } + for env in response.json().get('envs', []) + } + + +def delete_env_var(token, project_id, env_var_id): + """Delete a Vercel environment variable using its ID.""" + url = f'{VERCEL_API_BASE_URL}/v9/projects/{project_id}/env/{env_var_id}' + response = requests.delete(url, headers=get_vercel_headers(token)) + + if response.status_code != 200: + raise Exception(f"Error deleting environment variable: {response.text}") + + +def sync_vercel_secrets( + secrets, + credential_id, + project_id, + environment="production", + secret_type="encrypted", +): + """ + Sync secrets to a Vercel project. + + Args: + secrets (list of tuple): List of (key, value, comment) tuples to sync + credential_id (str): The ID of the stored credentials + project_id (str): The Vercel project ID + environment (str): Target environment (development/preview/production/all) + secret_type (str): Type of secret (plain/encrypted/sensitive) + + Returns: + tuple: (bool, dict) indicating success/failure and a message + """ + try: + token = get_vercel_credentials(credential_id) + + # Determine target environments + target_environments = ( + ['production', 'preview', 'development'] + if environment == 'all' + else [environment] + ) + + # Get existing environment variables + existing_env_vars = get_existing_env_vars(token, project_id) + + # Prepare payload for bulk creation + payload = [] + for key, value, comment in secrets: + # Check if the environment variable exists and needs updating + if key in existing_env_vars: + existing_var = existing_env_vars[key] + if ( + value != existing_var['value'] + or target_environments != existing_var['target'] + or comment != existing_var.get('comment') + ): + delete_env_var(token, project_id, existing_var['id']) + + env_var = { + 'key': key, + 'value': value, + 'type': secret_type, + 'target': target_environments, + } + if comment: + env_var['comment'] = comment + payload.append(env_var) + + # Delete environment variables not in the source + for key, env_var in existing_env_vars.items(): + if not any(s[0] == key for s in secrets): + delete_env_var(token, project_id, env_var['id']) + + # Bulk create environment variables + if payload: + url = f'{VERCEL_API_BASE_URL}/v10/projects/{project_id}/env?upsert=true' + response = requests.post( + url, headers=get_vercel_headers(token), json=payload + ) + + if response.status_code != 201: + raise Exception(f"Error creating environment variables: {response.text}") + + return True, { + "message": f"Successfully synced secrets to Vercel project environments: {', '.join(target_environments)}" + } + + except Exception as e: + return False, {"message": f"Failed to sync secrets: {str(e)}"} diff --git a/backend/backend/graphene/mutations/syncing.py b/backend/backend/graphene/mutations/syncing.py index 7bd52e1be..6714d2951 100644 --- a/backend/backend/graphene/mutations/syncing.py +++ b/backend/backend/graphene/mutations/syncing.py @@ -26,6 +26,9 @@ class RailwayResourceInput(graphene.InputObjectType): id = graphene.ID(required=True) name = graphene.String(required=True) +class VercelResourceInput(graphene.InputObjectType): + id = graphene.ID(required=True) + name = graphene.String(required=True) class InitEnvSync(graphene.Mutation): class Arguments: @@ -538,6 +541,67 @@ def mutate( return CreateRailwaySync(sync=sync) +class CreateVercelSync(graphene.Mutation): + class Arguments: + env_id = graphene.ID() + path = graphene.String() + credential_id = graphene.ID() + project_id = graphene.String() + project_name = graphene.String() + environment = graphene.String() + secret_type = graphene.String() + + sync = graphene.Field(EnvironmentSyncType) + + @classmethod + def mutate( + cls, + root, + info, + env_id, + path, + credential_id, + project_id, + project_name, + environment="production", + secret_type="encrypted", + ): + service_id = "vercel" + + env = Environment.objects.get(id=env_id) + + if not env.app.sse_enabled: + raise GraphQLError("Syncing is not enabled for this environment!") + + if not user_can_access_app(info.context.user.userId, env.app.id): + raise GraphQLError("You don't have access to this app") + + sync_options = { + "project": {"id": project_id, "name": project_name}, + "environment": environment, + "secret_type": secret_type, + } + + existing_syncs = EnvironmentSync.objects.filter( + environment__app_id=env.app.id, service=service_id, deleted_at=None + ) + + for es in existing_syncs: + if es.options == sync_options: + raise GraphQLError("A sync already exists for this Vercel project!") + + sync = EnvironmentSync.objects.create( + environment=env, + path=normalize_path_string(path), + service=service_id, + options=sync_options, + authentication_id=credential_id, + ) + + trigger_sync_tasks(sync) + + return CreateVercelSync(sync=sync) + class DeleteSync(graphene.Mutation): class Arguments: sync_id = graphene.ID() diff --git a/backend/backend/graphene/queries/syncing.py b/backend/backend/graphene/queries/syncing.py index 02ddf60e4..98b85506b 100644 --- a/backend/backend/graphene/queries/syncing.py +++ b/backend/backend/graphene/queries/syncing.py @@ -27,6 +27,10 @@ from api.utils.syncing.railway.main import ( fetch_railway_projects, ) +from api.utils.syncing.vercel.main import ( + test_vercel_creds, + list_vercel_projects +) from backend.graphene.types import ProviderType, ServiceType from graphql import GraphQLError @@ -162,6 +166,18 @@ def resolve_railway_projects(root, info, credential_id): except Exception as ex: raise GraphQLError(f"Error listing Railway environments: {str(ex)}") +def resolve_vercel_projects(root, info, credential_id): + """Resolver for listing Vercel projects.""" + try: + if not test_vercel_creds(credential_id): + raise GraphQLError( + "Could not authenticate with Vercel. Please check that your credentials are valid" + ) + + projects = list_vercel_projects(credential_id) + return projects + except Exception as ex: + raise GraphQLError(f"Error listing Vercel projects: {str(ex)}") def resolve_syncs(root, info, app_id=None, env_id=None, org_id=None): diff --git a/backend/backend/schema.py b/backend/backend/schema.py index 4035d530c..e82982108 100644 --- a/backend/backend/schema.py +++ b/backend/backend/schema.py @@ -3,6 +3,11 @@ from api.utils.syncing.github.actions import GitHubRepoType from api.utils.syncing.gitlab.main import GitLabGroupType, GitLabProjectType from api.utils.syncing.railway.main import RailwayProjectType +from api.utils.syncing.vercel.main import VercelProjectType +from .graphene.queries.syncing import ( + resolve_vercel_projects, +) +from .graphene.mutations.syncing import CreateVercelSync from .graphene.mutations.access import ( CreateCustomRoleMutation, DeleteCustomRoleMutation, @@ -271,6 +276,10 @@ class Query(graphene.ObjectType): railway_projects = graphene.List(RailwayProjectType, credential_id=graphene.ID()) + vercel_projects = graphene.List(VercelProjectType, credential_id=graphene.ID()) + + test_vercel_creds = graphene.Field(graphene.Boolean, credential_id=graphene.ID()) + test_vault_creds = graphene.Field(graphene.Boolean, credential_id=graphene.ID()) test_nomad_creds = graphene.Field(graphene.Boolean, credential_id=graphene.ID()) @@ -305,6 +314,7 @@ class Query(graphene.ObjectType): resolve_gitlab_groups = resolve_gitlab_groups resolve_railway_projects = resolve_railway_projects + resolve_vercel_projects = resolve_vercel_projects resolve_test_vault_creds = resolve_test_vault_creds @@ -797,6 +807,9 @@ class Mutation(graphene.ObjectType): # Railway create_railway_sync = CreateRailwaySync.Field() + # Vercel + create_vercel_sync = CreateVercelSync.Field() + create_user_token = CreateUserTokenMutation.Field() delete_user_token = DeleteUserTokenMutation.Field() diff --git a/backend/version.txt b/backend/version.txt index ffeebe963..ee14c9d6e 100644 --- a/backend/version.txt +++ b/backend/version.txt @@ -1 +1 @@ -v2.32.5 +v2.33.0 diff --git a/frontend/apollo/gql.ts b/frontend/apollo/gql.ts index a59f992b0..f9a5f97d4 100644 --- a/frontend/apollo/gql.ts +++ b/frontend/apollo/gql.ts @@ -65,6 +65,7 @@ const documents = { "mutation UpdateProviderCreds($credentialId: ID!, $name: String!, $credentials: JSONString!) {\n updateProviderCredentials(\n credentialId: $credentialId\n name: $name\n credentials: $credentials\n ) {\n credential {\n id\n }\n }\n}": types.UpdateProviderCredsDocument, "mutation UpdateSyncAuth($syncId: ID!, $credentialId: ID!) {\n updateSyncAuthentication(syncId: $syncId, credentialId: $credentialId) {\n sync {\n id\n status\n }\n }\n}": types.UpdateSyncAuthDocument, "mutation CreateNewVaultSync($envId: ID!, $path: String!, $engine: String!, $vaultPath: String!, $credentialId: ID!) {\n createVaultSync(\n envId: $envId\n path: $path\n engine: $engine\n vaultPath: $vaultPath\n credentialId: $credentialId\n ) {\n sync {\n id\n environment {\n id\n name\n envType\n }\n serviceInfo {\n id\n name\n }\n isActive\n lastSync\n createdAt\n }\n }\n}": types.CreateNewVaultSyncDocument, + "mutation CreateNewVercelSync($envId: ID!, $path: String!, $credentialId: ID!, $projectId: String!, $projectName: String!, $environment: String!, $secretType: String!) {\n createVercelSync(\n envId: $envId\n path: $path\n credentialId: $credentialId\n projectId: $projectId\n projectName: $projectName\n environment: $environment\n secretType: $secretType\n ) {\n sync {\n id\n environment {\n id\n name\n envType\n }\n serviceInfo {\n id\n name\n }\n isActive\n lastSync\n createdAt\n }\n }\n}": types.CreateNewVercelSyncDocument, "mutation CreateNewUserToken($orgId: ID!, $name: String!, $identityKey: String!, $token: String!, $wrappedKeyShare: String!, $expiry: BigInt) {\n createUserToken(\n orgId: $orgId\n name: $name\n identityKey: $identityKey\n token: $token\n wrappedKeyShare: $wrappedKeyShare\n expiry: $expiry\n ) {\n ok\n }\n}": types.CreateNewUserTokenDocument, "mutation RevokeUserToken($tokenId: ID!) {\n deleteUserToken(tokenId: $tokenId) {\n ok\n }\n}": types.RevokeUserTokenDocument, "query GetAppMembers($appId: ID!) {\n appUsers(appId: $appId) {\n id\n identityKey\n email\n fullName\n avatarUrl\n createdAt\n role {\n id\n name\n description\n permissions\n color\n }\n }\n}": types.GetAppMembersDocument, @@ -106,6 +107,7 @@ const documents = { "query TestNomadAuth($credentialId: ID!) {\n testNomadCreds(credentialId: $credentialId)\n}": types.TestNomadAuthDocument, "query GetRailwayProjects($credentialId: ID!) {\n railwayProjects(credentialId: $credentialId) {\n id\n name\n environments {\n id\n name\n }\n services {\n id\n name\n }\n }\n}": types.GetRailwayProjectsDocument, "query TestVaultAuth($credentialId: ID!) {\n testVaultCreds(credentialId: $credentialId)\n}": types.TestVaultAuthDocument, + "query GetVercelProjects($credentialId: ID!) {\n vercelProjects(credentialId: $credentialId) {\n id\n name\n environment\n }\n}": types.GetVercelProjectsDocument, "query GetUserTokens($organisationId: ID!) {\n userTokens(organisationId: $organisationId) {\n id\n name\n wrappedKeyShare\n createdAt\n expiresAt\n }\n}": types.GetUserTokensDocument, }; @@ -331,6 +333,10 @@ export function graphql(source: "mutation UpdateSyncAuth($syncId: ID!, $credenti * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "mutation CreateNewVaultSync($envId: ID!, $path: String!, $engine: String!, $vaultPath: String!, $credentialId: ID!) {\n createVaultSync(\n envId: $envId\n path: $path\n engine: $engine\n vaultPath: $vaultPath\n credentialId: $credentialId\n ) {\n sync {\n id\n environment {\n id\n name\n envType\n }\n serviceInfo {\n id\n name\n }\n isActive\n lastSync\n createdAt\n }\n }\n}"): (typeof documents)["mutation CreateNewVaultSync($envId: ID!, $path: String!, $engine: String!, $vaultPath: String!, $credentialId: ID!) {\n createVaultSync(\n envId: $envId\n path: $path\n engine: $engine\n vaultPath: $vaultPath\n credentialId: $credentialId\n ) {\n sync {\n id\n environment {\n id\n name\n envType\n }\n serviceInfo {\n id\n name\n }\n isActive\n lastSync\n createdAt\n }\n }\n}"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "mutation CreateNewVercelSync($envId: ID!, $path: String!, $credentialId: ID!, $projectId: String!, $projectName: String!, $environment: String!, $secretType: String!) {\n createVercelSync(\n envId: $envId\n path: $path\n credentialId: $credentialId\n projectId: $projectId\n projectName: $projectName\n environment: $environment\n secretType: $secretType\n ) {\n sync {\n id\n environment {\n id\n name\n envType\n }\n serviceInfo {\n id\n name\n }\n isActive\n lastSync\n createdAt\n }\n }\n}"): (typeof documents)["mutation CreateNewVercelSync($envId: ID!, $path: String!, $credentialId: ID!, $projectId: String!, $projectName: String!, $environment: String!, $secretType: String!) {\n createVercelSync(\n envId: $envId\n path: $path\n credentialId: $credentialId\n projectId: $projectId\n projectName: $projectName\n environment: $environment\n secretType: $secretType\n ) {\n sync {\n id\n environment {\n id\n name\n envType\n }\n serviceInfo {\n id\n name\n }\n isActive\n lastSync\n createdAt\n }\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -495,6 +501,10 @@ export function graphql(source: "query GetRailwayProjects($credentialId: ID!) {\ * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "query TestVaultAuth($credentialId: ID!) {\n testVaultCreds(credentialId: $credentialId)\n}"): (typeof documents)["query TestVaultAuth($credentialId: ID!) {\n testVaultCreds(credentialId: $credentialId)\n}"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "query GetVercelProjects($credentialId: ID!) {\n vercelProjects(credentialId: $credentialId) {\n id\n name\n environment\n }\n}"): (typeof documents)["query GetVercelProjects($credentialId: ID!) {\n vercelProjects(credentialId: $credentialId) {\n id\n name\n environment\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/frontend/apollo/graphql.ts b/frontend/apollo/graphql.ts index 9ee4c97d3..d1e475dba 100644 --- a/frontend/apollo/graphql.ts +++ b/frontend/apollo/graphql.ts @@ -147,7 +147,7 @@ export type AppType = { identityKey: Scalars['String']['output']; members: Array>; name: Scalars['String']['output']; - sseEnabled?: Maybe; + sseEnabled: Scalars['Boolean']['output']; wrappedKeyShare: Scalars['String']['output']; }; @@ -296,6 +296,11 @@ export type CreateVaultSync = { sync?: Maybe; }; +export type CreateVercelSync = { + __typename?: 'CreateVercelSync'; + sync?: Maybe; +}; + export type DeleteAppMutation = { __typename?: 'DeleteAppMutation'; ok?: Maybe; @@ -577,6 +582,7 @@ export type Mutation = { createServiceToken?: Maybe; createUserToken?: Maybe; createVaultSync?: Maybe; + createVercelSync?: Maybe; deleteApp?: Maybe; deleteCustomRole?: Maybe; deleteEnvSync?: Maybe; @@ -819,6 +825,17 @@ export type MutationCreateVaultSyncArgs = { }; +export type MutationCreateVercelSyncArgs = { + credentialId?: InputMaybe; + envId?: InputMaybe; + environment?: InputMaybe; + path?: InputMaybe; + projectId?: InputMaybe; + projectName?: InputMaybe; + secretType?: InputMaybe; +}; + + export type MutationDeleteAppArgs = { id: Scalars['ID']['input']; }; @@ -1156,8 +1173,10 @@ export type Query = { syncs?: Maybe>>; testNomadCreds?: Maybe; testVaultCreds?: Maybe; + testVercelCreds?: Maybe; userTokens?: Maybe>>; validateInvite?: Maybe; + vercelProjects?: Maybe>>; }; @@ -1346,6 +1365,11 @@ export type QueryTestVaultCredsArgs = { }; +export type QueryTestVercelCredsArgs = { + credentialId?: InputMaybe; +}; + + export type QueryUserTokensArgs = { organisationId?: InputMaybe; }; @@ -1355,6 +1379,11 @@ export type QueryValidateInviteArgs = { inviteId?: InputMaybe; }; + +export type QueryVercelProjectsArgs = { + credentialId?: InputMaybe; +}; + export type RailwayEnvironmentType = { __typename?: 'RailwayEnvironmentType'; id: Scalars['ID']['output']; @@ -1586,6 +1615,13 @@ export type UserTokenType = { wrappedKeyShare: Scalars['String']['output']; }; +export type VercelProjectType = { + __typename?: 'VercelProjectType'; + environment?: Maybe>>; + id: Scalars['ID']['output']; + name: Scalars['String']['output']; +}; + export type CreateRoleMutationVariables = Exact<{ name: Scalars['String']['input']; description: Scalars['String']['input']; @@ -1980,7 +2016,7 @@ export type InitAppSyncingMutationVariables = Exact<{ }>; -export type InitAppSyncingMutation = { __typename?: 'Mutation', initEnvSync?: { __typename?: 'InitEnvSync', app?: { __typename?: 'AppType', id: string, sseEnabled?: boolean | null } | null } | null }; +export type InitAppSyncingMutation = { __typename?: 'Mutation', initEnvSync?: { __typename?: 'InitEnvSync', app?: { __typename?: 'AppType', id: string, sseEnabled: boolean } | null } | null }; export type CreateNewNomadSyncMutationVariables = Exact<{ envId: Scalars['ID']['input']; @@ -2057,6 +2093,19 @@ export type CreateNewVaultSyncMutationVariables = Exact<{ export type CreateNewVaultSyncMutation = { __typename?: 'Mutation', createVaultSync?: { __typename?: 'CreateVaultSync', sync?: { __typename?: 'EnvironmentSyncType', id: string, isActive: boolean, lastSync?: any | null, createdAt?: any | null, environment: { __typename?: 'EnvironmentType', id: string, name: string, envType: ApiEnvironmentEnvTypeChoices }, serviceInfo?: { __typename?: 'ServiceType', id?: string | null, name?: string | null } | null } | null } | null }; +export type CreateNewVercelSyncMutationVariables = Exact<{ + envId: Scalars['ID']['input']; + path: Scalars['String']['input']; + credentialId: Scalars['ID']['input']; + projectId: Scalars['String']['input']; + projectName: Scalars['String']['input']; + environment: Scalars['String']['input']; + secretType: Scalars['String']['input']; +}>; + + +export type CreateNewVercelSyncMutation = { __typename?: 'Mutation', createVercelSync?: { __typename?: 'CreateVercelSync', sync?: { __typename?: 'EnvironmentSyncType', id: string, isActive: boolean, lastSync?: any | null, createdAt?: any | null, environment: { __typename?: 'EnvironmentType', id: string, name: string, envType: ApiEnvironmentEnvTypeChoices }, serviceInfo?: { __typename?: 'ServiceType', id?: string | null, name?: string | null } | null } | null } | null }; + export type CreateNewUserTokenMutationVariables = Exact<{ orgId: Scalars['ID']['input']; name: Scalars['String']['input']; @@ -2104,7 +2153,7 @@ export type GetAppDetailQueryVariables = Exact<{ }>; -export type GetAppDetailQuery = { __typename?: 'Query', apps?: Array<{ __typename?: 'AppType', id: string, name: string, identityKey: string, createdAt?: any | null, appToken: string, appSeed: string, appVersion: number, sseEnabled?: boolean | null } | null> | null }; +export type GetAppDetailQuery = { __typename?: 'Query', apps?: Array<{ __typename?: 'AppType', id: string, name: string, identityKey: string, createdAt?: any | null, appToken: string, appSeed: string, appVersion: number, sseEnabled: boolean } | null> | null }; export type GetAppKmsLogsQueryVariables = Exact<{ appId: Scalars['ID']['input']; @@ -2121,14 +2170,14 @@ export type GetAppsQueryVariables = Exact<{ }>; -export type GetAppsQuery = { __typename?: 'Query', apps?: Array<{ __typename?: 'AppType', id: string, name: string, identityKey: string, createdAt?: any | null, sseEnabled?: boolean | null, members: Array<{ __typename?: 'OrganisationMemberType', id: string, email?: string | null, fullName?: string | null, avatarUrl?: string | null } | null>, environments: Array<{ __typename?: 'EnvironmentType', id: string, name: string, envType: ApiEnvironmentEnvTypeChoices, syncs: Array<{ __typename?: 'EnvironmentSyncType', id: string, status: ApiEnvironmentSyncStatusChoices, serviceInfo?: { __typename?: 'ServiceType', id?: string | null, name?: string | null, provider?: { __typename?: 'ProviderType', id: string, name: string } | null } | null } | null> } | null> } | null> | null }; +export type GetAppsQuery = { __typename?: 'Query', apps?: Array<{ __typename?: 'AppType', id: string, name: string, identityKey: string, createdAt?: any | null, sseEnabled: boolean, members: Array<{ __typename?: 'OrganisationMemberType', id: string, email?: string | null, fullName?: string | null, avatarUrl?: string | null } | null>, environments: Array<{ __typename?: 'EnvironmentType', id: string, name: string, envType: ApiEnvironmentEnvTypeChoices, syncs: Array<{ __typename?: 'EnvironmentSyncType', id: string, status: ApiEnvironmentSyncStatusChoices, serviceInfo?: { __typename?: 'ServiceType', id?: string | null, name?: string | null, provider?: { __typename?: 'ProviderType', id: string, name: string } | null } | null } | null> } | null> } | null> | null }; export type GetDashboardQueryVariables = Exact<{ organisationId: Scalars['ID']['input']; }>; -export type GetDashboardQuery = { __typename?: 'Query', apps?: Array<{ __typename?: 'AppType', id: string, sseEnabled?: boolean | null } | null> | null, userTokens?: Array<{ __typename?: 'UserTokenType', id: string } | null> | null, organisationInvites?: Array<{ __typename?: 'OrganisationMemberInviteType', id: string } | null> | null, organisationMembers?: Array<{ __typename?: 'OrganisationMemberType', id: string } | null> | null, savedCredentials?: Array<{ __typename?: 'ProviderCredentialsType', id: string } | null> | null, syncs?: Array<{ __typename?: 'EnvironmentSyncType', id: string } | null> | null }; +export type GetDashboardQuery = { __typename?: 'Query', apps?: Array<{ __typename?: 'AppType', id: string, sseEnabled: boolean } | null> | null, userTokens?: Array<{ __typename?: 'UserTokenType', id: string } | null> | null, organisationInvites?: Array<{ __typename?: 'OrganisationMemberInviteType', id: string } | null> | null, organisationMembers?: Array<{ __typename?: 'OrganisationMemberType', id: string } | null> | null, savedCredentials?: Array<{ __typename?: 'ProviderCredentialsType', id: string } | null> | null, syncs?: Array<{ __typename?: 'EnvironmentSyncType', id: string } | null> | null }; export type GetOrganisationsQueryVariables = Exact<{ [key: string]: never; }>; @@ -2272,7 +2321,7 @@ export type GetOrganisationSyncsQueryVariables = Exact<{ }>; -export type GetOrganisationSyncsQuery = { __typename?: 'Query', syncs?: Array<{ __typename?: 'EnvironmentSyncType', id: string, path: string, options: any, isActive: boolean, lastSync?: any | null, status: ApiEnvironmentSyncStatusChoices, createdAt?: any | null, environment: { __typename?: 'EnvironmentType', id: string, name: string, envType: ApiEnvironmentEnvTypeChoices, app: { __typename?: 'AppType', id: string, name: string } }, serviceInfo?: { __typename?: 'ServiceType', id?: string | null, name?: string | null, provider?: { __typename?: 'ProviderType', id: string } | null } | null, authentication?: { __typename?: 'ProviderCredentialsType', id: string, name: string, credentials: any } | null, history: Array<{ __typename?: 'EnvironmentSyncEventType', id: string, status: ApiEnvironmentSyncEventStatusChoices, createdAt?: any | null, completedAt?: any | null, meta?: any | null }> } | null> | null, savedCredentials?: Array<{ __typename?: 'ProviderCredentialsType', id: string, name: string, credentials: any, createdAt?: any | null, syncCount?: number | null, provider?: { __typename?: 'ProviderType', id: string, name: string, expectedCredentials: Array, optionalCredentials: Array } | null } | null> | null, apps?: Array<{ __typename?: 'AppType', id: string, name: string, identityKey: string, createdAt?: any | null, sseEnabled?: boolean | null, members: Array<{ __typename?: 'OrganisationMemberType', id: string } | null>, environments: Array<{ __typename?: 'EnvironmentType', id: string, name: string, syncs: Array<{ __typename?: 'EnvironmentSyncType', id: string, status: ApiEnvironmentSyncStatusChoices, serviceInfo?: { __typename?: 'ServiceType', id?: string | null, name?: string | null, provider?: { __typename?: 'ProviderType', id: string, name: string } | null } | null } | null> } | null> } | null> | null }; +export type GetOrganisationSyncsQuery = { __typename?: 'Query', syncs?: Array<{ __typename?: 'EnvironmentSyncType', id: string, path: string, options: any, isActive: boolean, lastSync?: any | null, status: ApiEnvironmentSyncStatusChoices, createdAt?: any | null, environment: { __typename?: 'EnvironmentType', id: string, name: string, envType: ApiEnvironmentEnvTypeChoices, app: { __typename?: 'AppType', id: string, name: string } }, serviceInfo?: { __typename?: 'ServiceType', id?: string | null, name?: string | null, provider?: { __typename?: 'ProviderType', id: string } | null } | null, authentication?: { __typename?: 'ProviderCredentialsType', id: string, name: string, credentials: any } | null, history: Array<{ __typename?: 'EnvironmentSyncEventType', id: string, status: ApiEnvironmentSyncEventStatusChoices, createdAt?: any | null, completedAt?: any | null, meta?: any | null }> } | null> | null, savedCredentials?: Array<{ __typename?: 'ProviderCredentialsType', id: string, name: string, credentials: any, createdAt?: any | null, syncCount?: number | null, provider?: { __typename?: 'ProviderType', id: string, name: string, expectedCredentials: Array, optionalCredentials: Array } | null } | null> | null, apps?: Array<{ __typename?: 'AppType', id: string, name: string, identityKey: string, createdAt?: any | null, sseEnabled: boolean, members: Array<{ __typename?: 'OrganisationMemberType', id: string } | null>, environments: Array<{ __typename?: 'EnvironmentType', id: string, name: string, syncs: Array<{ __typename?: 'EnvironmentSyncType', id: string, status: ApiEnvironmentSyncStatusChoices, serviceInfo?: { __typename?: 'ServiceType', id?: string | null, name?: string | null, provider?: { __typename?: 'ProviderType', id: string, name: string } | null } | null } | null> } | null> } | null> | null }; export type GetAwsSecretsQueryVariables = Exact<{ credentialId: Scalars['ID']['input']; @@ -2352,6 +2401,13 @@ export type TestVaultAuthQueryVariables = Exact<{ export type TestVaultAuthQuery = { __typename?: 'Query', testVaultCreds?: boolean | null }; +export type GetVercelProjectsQueryVariables = Exact<{ + credentialId: Scalars['ID']['input']; +}>; + + +export type GetVercelProjectsQuery = { __typename?: 'Query', vercelProjects?: Array<{ __typename?: 'VercelProjectType', id: string, name: string, environment?: Array | null } | null> | null }; + export type GetUserTokensQueryVariables = Exact<{ organisationId: Scalars['ID']['input']; }>; @@ -2412,6 +2468,7 @@ export const TriggerEnvSyncDocument = {"kind":"Document","definitions":[{"kind": export const UpdateProviderCredsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateProviderCreds"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"credentials"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSONString"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateProviderCredentials"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"credentialId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"credentials"},"value":{"kind":"Variable","name":{"kind":"Name","value":"credentials"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"credential"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const UpdateSyncAuthDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateSyncAuth"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"syncId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateSyncAuthentication"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"syncId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"syncId"}}},{"kind":"Argument","name":{"kind":"Name","value":"credentialId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sync"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]}}]} as unknown as DocumentNode; export const CreateNewVaultSyncDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateNewVaultSync"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"envId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"path"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"engine"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"vaultPath"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createVaultSync"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"envId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"envId"}}},{"kind":"Argument","name":{"kind":"Name","value":"path"},"value":{"kind":"Variable","name":{"kind":"Name","value":"path"}}},{"kind":"Argument","name":{"kind":"Name","value":"engine"},"value":{"kind":"Variable","name":{"kind":"Name","value":"engine"}}},{"kind":"Argument","name":{"kind":"Name","value":"vaultPath"},"value":{"kind":"Variable","name":{"kind":"Name","value":"vaultPath"}}},{"kind":"Argument","name":{"kind":"Name","value":"credentialId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sync"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"environment"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"envType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"serviceInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"lastSync"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode; +export const CreateNewVercelSyncDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateNewVercelSync"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"envId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"path"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"environment"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"secretType"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createVercelSync"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"envId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"envId"}}},{"kind":"Argument","name":{"kind":"Name","value":"path"},"value":{"kind":"Variable","name":{"kind":"Name","value":"path"}}},{"kind":"Argument","name":{"kind":"Name","value":"credentialId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}}},{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}},{"kind":"Argument","name":{"kind":"Name","value":"projectName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectName"}}},{"kind":"Argument","name":{"kind":"Name","value":"environment"},"value":{"kind":"Variable","name":{"kind":"Name","value":"environment"}}},{"kind":"Argument","name":{"kind":"Name","value":"secretType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"secretType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sync"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"environment"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"envType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"serviceInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"lastSync"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode; export const CreateNewUserTokenDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateNewUserToken"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orgId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"identityKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"wrappedKeyShare"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"expiry"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createUserToken"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"orgId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orgId"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"identityKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"identityKey"}}},{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}},{"kind":"Argument","name":{"kind":"Name","value":"wrappedKeyShare"},"value":{"kind":"Variable","name":{"kind":"Name","value":"wrappedKeyShare"}}},{"kind":"Argument","name":{"kind":"Name","value":"expiry"},"value":{"kind":"Variable","name":{"kind":"Name","value":"expiry"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; export const RevokeUserTokenDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RevokeUserToken"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"tokenId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteUserToken"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"tokenId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"tokenId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; export const GetAppMembersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAppMembers"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appUsers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"identityKey"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"fullName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"}},{"kind":"Field","name":{"kind":"Name","value":"color"}}]}}]}}]}}]} as unknown as DocumentNode; @@ -2453,4 +2510,5 @@ export const GetGitLabResourcesDocument = {"kind":"Document","definitions":[{"ki export const TestNomadAuthDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"TestNomadAuth"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"testNomadCreds"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"credentialId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}}}]}]}}]} as unknown as DocumentNode; export const GetRailwayProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetRailwayProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"railwayProjects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"credentialId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"environments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"services"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; export const TestVaultAuthDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"TestVaultAuth"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"testVaultCreds"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"credentialId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}}}]}]}}]} as unknown as DocumentNode; +export const GetVercelProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetVercelProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"vercelProjects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"credentialId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"credentialId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"environment"}}]}}]}}]} as unknown as DocumentNode; export const GetUserTokensDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserTokens"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userTokens"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"organisationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"wrappedKeyShare"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/frontend/apollo/schema.graphql b/frontend/apollo/schema.graphql index 47a4f6b26..34cbdf77c 100644 --- a/frontend/apollo/schema.graphql +++ b/frontend/apollo/schema.graphql @@ -37,6 +37,8 @@ type Query { gitlabProjects(credentialId: ID): [GitLabProjectType] gitlabGroups(credentialId: ID): [GitLabGroupType] railwayProjects(credentialId: ID): [RailwayProjectType] + vercelProjects(credentialId: ID): [VercelProjectType] + testVercelCreds(credentialId: ID): Boolean testVaultCreds(credentialId: ID): Boolean testNomadCreds(credentialId: ID): Boolean stripeCheckoutDetails(stripeSessionId: String!): StripeCheckoutDetails @@ -191,7 +193,7 @@ type AppType { appSeed: String! wrappedKeyShare: String! createdAt: DateTime - sseEnabled: Boolean + sseEnabled: Boolean! environments: [EnvironmentType]! members: [OrganisationMemberType]! } @@ -578,6 +580,12 @@ type RailwayServiceType { name: String! } +type VercelProjectType { + id: ID! + name: String! + environment: [String] +} + type StripeCheckoutDetails { paymentStatus: String customerEmail: String @@ -625,6 +633,7 @@ type Mutation { createNomadSync(credentialId: ID, envId: ID, nomadNamespace: String, nomadPath: String, path: String): CreateNomadSync createGitlabCiSync(credentialId: ID, envId: ID, isGroup: Boolean, masked: Boolean, path: String, protected: Boolean, resourceId: String, resourcePath: String): CreateGitLabCISync createRailwaySync(credentialId: ID, envId: ID, path: String, railwayEnvironment: RailwayResourceInput, railwayProject: RailwayResourceInput, railwayService: RailwayResourceInput): CreateRailwaySync + createVercelSync(credentialId: ID, envId: ID, environment: String, path: String, projectId: String, projectName: String, secretType: String): CreateVercelSync createUserToken(expiry: BigInt, identityKey: String!, name: String!, orgId: ID!, token: String!, wrappedKeyShare: String!): CreateUserTokenMutation deleteUserToken(tokenId: ID!): DeleteUserTokenMutation createServiceToken(appId: ID!, environmentKeys: [EnvironmentKeyInput], expiry: BigInt, identityKey: String!, name: String!, token: String!, wrappedKeyShare: String!): CreateServiceTokenMutation @@ -815,6 +824,10 @@ input RailwayResourceInput { name: String! } +type CreateVercelSync { + sync: EnvironmentSyncType +} + type CreateUserTokenMutation { ok: Boolean userToken: UserTokenType diff --git a/frontend/components/syncing/CreateProviderCredentials.tsx b/frontend/components/syncing/CreateProviderCredentials.tsx index 4834bc636..95c942454 100644 --- a/frontend/components/syncing/CreateProviderCredentials.tsx +++ b/frontend/components/syncing/CreateProviderCredentials.tsx @@ -103,6 +103,10 @@ export const CreateProviderCredentials = (props: { return 'https://docs.phase.dev/integrations/platforms/github-actions' else if (provider.id === 'gitlab') return 'https://docs.phase.dev/integrations/platforms/gitlab-ci' + else if (provider.id === 'railway') + return 'https://docs.phase.dev/integrations/platforms/railway' + else if (provider.id === 'vercel') + return 'https://docs.phase.dev/integrations/platforms/vercel' else return 'https://docs.phase.dev/integrations' } diff --git a/frontend/components/syncing/CreateSyncDialog.tsx b/frontend/components/syncing/CreateSyncDialog.tsx index b50c27a19..3b43e66bb 100644 --- a/frontend/components/syncing/CreateSyncDialog.tsx +++ b/frontend/components/syncing/CreateSyncDialog.tsx @@ -10,6 +10,7 @@ import { CreateVaultSync } from './Vault/CreateVaultSync' import { CreateNomadSync } from './Nomad/CreateNomadSync' import { CreateGitLabCISync } from './GitLab/CreateGitLabCISync' import { CreateRailwaySync } from './Railway/CreateRailwaySync' +import { CreateVercelSync } from './Vercel/CreateVercelSync' import { organisationContext } from '@/contexts/organisationContext' import { userHasPermission } from '@/utils/access/permissions' import { EmptyState } from '../common/EmptyState' @@ -63,6 +64,8 @@ export const CreateSyncDialog = (props: { return case 'railway': return + case 'vercel': + return default: return null diff --git a/frontend/components/syncing/EnvSyncStatus.tsx b/frontend/components/syncing/EnvSyncStatus.tsx index a4edfeb85..6bd4bc306 100644 --- a/frontend/components/syncing/EnvSyncStatus.tsx +++ b/frontend/components/syncing/EnvSyncStatus.tsx @@ -50,11 +50,11 @@ export const EnvSyncStatus = (props: { leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - +
-
+
Syncs
diff --git a/frontend/components/syncing/ProviderIcon.tsx b/frontend/components/syncing/ProviderIcon.tsx index d3426d707..4fd77f577 100644 --- a/frontend/components/syncing/ProviderIcon.tsx +++ b/frontend/components/syncing/ProviderIcon.tsx @@ -7,6 +7,7 @@ import { SiNomad, SiRailway, SiVault, + SiVercel, } from 'react-icons/si' export const ProviderIcon = (props: { providerId: string }) => { @@ -32,5 +33,8 @@ export const ProviderIcon = (props: { providerId: string }) => { if (providerId.toLowerCase().includes('railway')) return + + if (providerId.toLowerCase().includes('vercel')) + return else return } diff --git a/frontend/components/syncing/ServiceInfo.tsx b/frontend/components/syncing/ServiceInfo.tsx index 4c2649a6a..5b4ff1264 100644 --- a/frontend/components/syncing/ServiceInfo.tsx +++ b/frontend/components/syncing/ServiceInfo.tsx @@ -1,4 +1,4 @@ -import { EnvironmentSyncType, RailwayProjectType, RailwayResourceInput } from '@/apollo/graphql' +import { EnvironmentSyncType, RailwayResourceInput, VercelProjectType } from '@/apollo/graphql' import { FaEyeSlash, FaLock } from 'react-icons/fa' export const ServiceInfo = (props: { sync: EnvironmentSyncType }) => { @@ -66,5 +66,17 @@ export const ServiceInfo = (props: { sync: EnvironmentSyncType }) => { {project.name} {service ? ` - ${service.name}` : ''} ({environment.name})
) + } else if (sync.serviceInfo?.id?.includes('vercel')) { + const project: VercelProjectType = JSON.parse(sync.options)['project'] + const environment = JSON.parse(sync.options)['environment'] + const secretType = JSON.parse(sync.options)['secret_type'] + + return ( +
+ {project.name} ({environment}) + {secretType === 'encrypted' && } + {secretType === 'sensitive' && } +
+ ) } else return <>{sync.serviceInfo?.id} } diff --git a/frontend/components/syncing/Vercel/CreateVercelSync.tsx b/frontend/components/syncing/Vercel/CreateVercelSync.tsx new file mode 100644 index 000000000..221c1b15d --- /dev/null +++ b/frontend/components/syncing/Vercel/CreateVercelSync.tsx @@ -0,0 +1,343 @@ +import GetVercelProjects from '@/graphql/queries/syncing/vercel/getProject.gql' +import GetAppSyncStatus from '@/graphql/queries/syncing/getAppSyncStatus.gql' +import GetAppEnvironments from '@/graphql/queries/secrets/getAppEnvironments.gql' +import GetSavedCredentials from '@/graphql/queries/syncing/getSavedCredentials.gql' +import CreateNewVercelSync from '@/graphql/mutations/syncing/vercel/createVercelSync.gql' +import { useLazyQuery, useMutation, useQuery } from '@apollo/client' +import { Fragment, useContext, useEffect, useState } from 'react' +import { Button } from '../../common/Button' +import { + EnvironmentType, + ProviderCredentialsType, + VercelProjectType +} from '@/apollo/graphql' +import { Combobox, RadioGroup, Transition } from '@headlessui/react' +import clsx from 'clsx' +import { FaAngleDoubleDown, FaChevronDown, FaCircle, FaDotCircle } from 'react-icons/fa' +import { toast } from 'react-toastify' +import { SiVercel } from 'react-icons/si' + +import { organisationContext } from '@/contexts/organisationContext' +import { ProviderCredentialPicker } from '../ProviderCredentialPicker' +import { Input } from '@/components/common/Input' + +export const CreateVercelSync = (props: { appId: string; closeModal: () => void }) => { + const { activeOrganisation: organisation } = useContext(organisationContext) + const { appId, closeModal } = props + + const { data: appEnvsData } = useQuery(GetAppEnvironments, { + variables: { appId } + }) + + const { data: credentialsData } = useQuery(GetSavedCredentials, { + variables: { orgId: organisation!.id } + }) + + const [getVercelProjects, { loading }] = useLazyQuery(GetVercelProjects) + + const [createVercelSync, { loading: creating }] = useMutation(CreateNewVercelSync) + + const [credential, setCredential] = useState(null) + const [vercelProjects, setVercelProjects] = useState([]) + const [vercelProject, setVercelProject] = useState(null) + const [projectQuery, setProjectQuery] = useState('') + + const [phaseEnv, setPhaseEnv] = useState(null) + const [path, setPath] = useState('/') + const [environment, setEnvironment] = useState('production') + const [secretType, setSecretType] = useState('encrypted') + + const [credentialsValid, setCredentialsValid] = useState(false) + + // Preselect first available credential + useEffect(() => { + if (credentialsData?.savedCredentials.length > 0) { + setCredential(credentialsData.savedCredentials[0]) + } + }, [credentialsData]) + + // Preselect first available environment + useEffect(() => { + if (appEnvsData?.appEnvironments.length > 0) { + setPhaseEnv(appEnvsData.appEnvironments[0]) + } + }, [appEnvsData]) + + const handleSubmit = async (e: { preventDefault: () => void }) => { + e.preventDefault() + + if (credential === null) { + toast.error('Please select credentials to use for this sync') + return false + } else if (!credentialsValid) { + try { + const { data: projectsData } = await getVercelProjects({ + variables: { credentialId: credential.id } + }) + if (projectsData?.vercelProjects) { + setVercelProjects(projectsData.vercelProjects) + setCredentialsValid(true) + } + } catch (error: any) { + toast.error(error.message) + } + } else if (!vercelProject) { + toast.error('Please select a Vercel project!') + return false + } else { + try { + await createVercelSync({ + variables: { + envId: phaseEnv?.id, + path, + credentialId: credential.id, + projectId: vercelProject.id, + projectName: vercelProject.name, + environment, + secretType + }, + refetchQueries: [{ query: GetAppSyncStatus, variables: { appId } }] + }) + + toast.success('Created new Sync!') + closeModal() + } catch (error: any) { + toast.error(error.message) + } + } + } + + + const filteredProjects = projectQuery === '' + ? vercelProjects + : vercelProjects.filter((project) => + project.name?.toLowerCase().includes(projectQuery.toLowerCase()) + ) + + const environments = ['production', 'preview', 'development', 'all'] + const secretTypes = [ + { value: 'plain', label: 'Plain Text' }, + { value: 'encrypted', label: 'Encrypted' }, + { value: 'sensitive', label: 'Sensitive' } + ] + + return ( +
+
+
+ + Vercel +
+
+ Sync an environment with Vercel. +
+
+ +
+ {!credentialsValid && ( +
+
+ Step 1: Choose authentication credentials +
+
+
+ setCredential(cred)} + orgId={organisation!.id} + providerFilter="vercel" + setDefault={true} + /> +
+
+
+ )} + + {credentialsValid && ( +
+
+ Step 2: Select source and destination for Secrets +
+ +
+ + + + +
+ {appEnvsData.appEnvironments.map((env: EnvironmentType) => ( + + {({ active, checked }) => ( +
+ {checked ? ( + + ) : ( + + )} + {env.name} +
+ )} +
+ ))} +
+
+ + +
+ +
+
+ +
+
+ +
+
+ + {({ open }) => ( + <> +
+ + + +
+ setProjectQuery(event.target.value)} + displayValue={(project: VercelProjectType) => project?.name!} + required + /> +
+ + + +
+
+
+ + +
+ {filteredProjects.map((project) => ( + + {({ active }) => ( +
+
+ {project.name} +
+
+ {project.id} +
+
+ )} +
+ ))} +
+
+
+ + )} +
+
+ +
+ + + + +
+ {environments.map((env) => ( + + {({ active, checked }) => ( +
+ {checked ? ( + + ) : ( + + )} + {env} +
+ )} +
+ ))} +
+
+
+ +
+ + + + +
+ {secretTypes.map((type) => ( + + {({ active, checked }) => ( +
+ {checked ? ( + + ) : ( + + )} + {type.label} +
+ )} +
+ ))} +
+
+
+
+
+ )} + +
+
+ {credentialsValid && ( + + )} +
+ +
+
+
+ ) +} diff --git a/frontend/graphql/mutations/syncing/vercel/createVercelSync.gql b/frontend/graphql/mutations/syncing/vercel/createVercelSync.gql new file mode 100644 index 000000000..569dbe969 --- /dev/null +++ b/frontend/graphql/mutations/syncing/vercel/createVercelSync.gql @@ -0,0 +1,35 @@ +mutation CreateNewVercelSync( + $envId: ID! + $path: String! + $credentialId: ID! + $projectId: String! + $projectName: String! + $environment: String! + $secretType: String! +) { + createVercelSync( + envId: $envId + path: $path + credentialId: $credentialId + projectId: $projectId + projectName: $projectName + environment: $environment + secretType: $secretType + ) { + sync { + id + environment { + id + name + envType + } + serviceInfo { + id + name + } + isActive + lastSync + createdAt + } + } +} \ No newline at end of file diff --git a/frontend/graphql/queries/syncing/vercel/getProject.gql b/frontend/graphql/queries/syncing/vercel/getProject.gql new file mode 100644 index 000000000..00abf0a62 --- /dev/null +++ b/frontend/graphql/queries/syncing/vercel/getProject.gql @@ -0,0 +1,7 @@ +query GetVercelProjects($credentialId: ID!) { + vercelProjects(credentialId: $credentialId) { + id + name + environment + } +} \ No newline at end of file