diff --git a/.github/resources/rename_migration_files.py b/.github/resources/rename_migration_files.py new file mode 100644 index 0000000000..5dd2667285 --- /dev/null +++ b/.github/resources/rename_migration_files.py @@ -0,0 +1,26 @@ +import os +from datetime import datetime, timedelta + +def rename_migrations(): + migration_folder = "./backend/src/db/migrations" + with open("added_files.txt", "r") as file: + changed_files = file.readlines() + + # Find the latest file among the changed files + latest_timestamp = datetime.now() # utc time + for file_path in changed_files: + file_path = file_path.strip() + # each new file bump by 1s + latest_timestamp = latest_timestamp + timedelta(seconds=1) + + new_filename = os.path.join(migration_folder, latest_timestamp.strftime("%Y%m%d%H%M%S") + f"_{file_path.split('_')[1]}") + old_filename = os.path.join(migration_folder, file_path) + os.rename(old_filename, new_filename) + print(f"Renamed {old_filename} to {new_filename}") + + if len(changed_files) == 0: + print("No new files added to migration folder") + +if __name__ == "__main__": + rename_migrations() + diff --git a/.github/workflows/update-be-new-migration-latest-timestamp.yml b/.github/workflows/update-be-new-migration-latest-timestamp.yml new file mode 100644 index 0000000000..160828473e --- /dev/null +++ b/.github/workflows/update-be-new-migration-latest-timestamp.yml @@ -0,0 +1,48 @@ +name: Rename Migrations + +on: + pull_request: + types: [closed] + paths: + - 'backend/src/db/migrations/**' + +jobs: + rename: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get list of newly added files in migration folder + run: | + git diff --name-status HEAD^ HEAD backend/src/db/migrations | grep '^A' | cut -f2 | xargs -n1 basename > added_files.txt + if [ ! -s added_files.txt ]; then + echo "No new files added. Skipping" + echo "SKIP_RENAME=true" >> $GITHUB_ENV + fi + + - name: Script to rename migrations + if: env.SKIP_RENAME != 'true' + run: python .github/resources/rename_migration_files.py + + - name: Commit and push changes + if: env.SKIP_RENAME != 'true' + run: | + git config user.name github-actions + git config user.email github-actions@github.com + git add ./backend/src/db/migrations + rm added_files.txt + git commit -m "chore: renamed new migration files to latest timestamp (gh-action)" + + - name: Create Pull Request + if: env.SKIP_RENAME != 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: renamed new migration files to latest UTC (gh-action)' + title: 'GH Action: rename new migration file timestamp' + branch-suffix: timestamp diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index 3d42943a6e..778589de86 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -572,14 +572,14 @@ export const integrationAuthServiceFactory = ({ const response = keys .Keys!.map((key) => { const keyAlias = aliases.Aliases!.find((alias) => key.KeyId === alias.TargetKeyId); - if (!keyAlias?.AliasName?.includes("alias/aws/") || keyAlias?.AliasName?.includes("alias/aws/secretsmanager")) { + if (!keyAlias?.AliasName?.includes("alias/aws/")) { return { id: String(key.KeyId), alias: String(keyAlias?.AliasName || key.KeyId) }; } return { id: "null", alias: "null" }; }) .filter((elem) => elem.id !== "null"); - return response; + return [...response, { id: "null", alias: "default" }]; }; const getQoveryProjects = async ({ diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index 5fc11c0387..de576dc992 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -477,24 +477,29 @@ const syncSecretsAWSParameterStore = async ({ }), {} as Record ); - // Identify secrets to create await Promise.all( Object.keys(secrets).map(async (key) => { if (!(key in awsParameterStoreSecretsObj)) { // case: secret does not exist in AWS parameter store // -> create secret - await ssm - .putParameter({ - Name: `${integration.path}${key}`, - Type: "SecureString", - Value: secrets[key].value, - // Overwrite: true, - Tags: metadata.secretAWSTag - ? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value })) - : [] - }) - .promise(); + if (secrets[key].value) { + await ssm + .putParameter({ + Name: `${integration.path}${key}`, + Type: "SecureString", + Value: secrets[key].value, + KeyId: metadata.kmsKeyId ? metadata.kmsKeyId : undefined, + // Overwrite: true, + Tags: metadata.secretAWSTag + ? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ + Key: tag.key, + Value: tag.value + })) + : [] + }) + .promise(); + } // case: secret exists in AWS parameter store } else if (awsParameterStoreSecretsObj[key].Value !== secrets[key].value) { // case: secret value doesn't match one in AWS parameter store diff --git a/.env.example b/docker-swarm/.env-example similarity index 84% rename from .env.example rename to docker-swarm/.env-example index bdb3e536d0..03d05a08e7 100644 --- a/.env.example +++ b/docker-swarm/.env-example @@ -8,16 +8,10 @@ ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 # THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE= -# Postgres creds -POSTGRES_PASSWORD=infisical -POSTGRES_USER=infisical -POSTGRES_DB=infisical - -# Required -DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} - +DB_CONNECTION_URI=postgres://infisical:infisical@haproxy:5433/infisical?sslmode=no-verify # Redis -REDIS_URL=redis://redis:6379 +REDIS_URL=redis://:123456@haproxy:6379 + # Website URL # Required diff --git a/docker-swarm/haproxy.cfg b/docker-swarm/haproxy.cfg new file mode 100644 index 0000000000..3717fedacc --- /dev/null +++ b/docker-swarm/haproxy.cfg @@ -0,0 +1,78 @@ +global + maxconn 10000 + log stdout format raw local0 + +defaults + log global + mode tcp + retries 3 + timeout client 30m + timeout connect 10s + timeout server 30m + timeout check 5s + +listen stats + mode http + bind *:7000 + stats enable + stats uri / + +resolvers hostdns + nameserver dns 127.0.0.11:53 + resolve_retries 3 + timeout resolve 1s + timeout retry 1s + hold valid 5s + +frontend master + bind *:5433 + default_backend master_backend + +frontend replicas + bind *:5434 + default_backend replica_backend + + +backend master_backend + option httpchk GET /master + http-check expect status 200 + default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions + server postgres-1 postgres-1:5432 check port 8008 resolvers hostdns + server postgres-2 postgres-2:5432 check port 8008 resolvers hostdns + server postgres-3 postgres-3:5432 check port 8008 resolvers hostdns + +backend replica_backend + option httpchk GET /replica + http-check expect status 200 + default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions + server postgres-1 postgres-1:5432 check port 8008 resolvers hostdns + server postgres-2 postgres-2:5432 check port 8008 resolvers hostdns + server postgres-3 postgres-3:5432 check port 8008 resolvers hostdns + + +frontend redis_frontend + bind *:6379 + default_backend redis_backend + +backend redis_backend + option tcp-check + tcp-check send AUTH\ 123456\r\n + tcp-check expect string +OK + tcp-check send PING\r\n + tcp-check expect string +PONG + tcp-check send info\ replication\r\n + tcp-check expect string role:master + tcp-check send QUIT\r\n + tcp-check expect string +OK + server redis_master redis_replica0:6379 check inter 1s + server redis_replica1 redis_replica1:6379 check inter 1s + server redis_replica2 redis_replica2:6379 check inter 1s + +frontend infisical_frontend + bind *:8080 + default_backend infisical_backend + +backend infisical_backend + option httpchk GET /api/status + http-check expect status 200 + server infisical infisical:8080 check inter 1s diff --git a/docker-swarm/stack.yaml b/docker-swarm/stack.yaml new file mode 100644 index 0000000000..11d5f5c624 --- /dev/null +++ b/docker-swarm/stack.yaml @@ -0,0 +1,259 @@ +version: "3" + +services: + haproxy: + image: haproxy:latest + ports: + - '7001:7000' + - '5002:5433' + - '5003:5434' + - '6379:6379' + - '8080:8080' + networks: + - infisical + configs: + - source: haproxy-config + target: /usr/local/etc/haproxy/haproxy.cfg + deploy: + placement: + constraints: + - node.labels.name == node1 + + infisical: + container_name: infisical-backend + image: infisical/infisical:latest-postgres + env_file: .env + ports: + - 80:8080 + environment: + - NODE_ENV=production + networks: + - infisical + secrets: + - env_file + + etcd1: + image: ghcr.io/zalando/spilo-16:3.2-p2 + networks: + - infisical + environment: + ETCD_UNSUPPORTED_ARCH: arm64 + container_name: demo-etcd1 + deploy: + placement: + constraints: + - node.labels.name == node1 + hostname: etcd1 + command: | + etcd --name etcd1 + --listen-client-urls http://0.0.0.0:2379 + --listen-peer-urls=http://0.0.0.0:2380 + --advertise-client-urls http://etcd1:2379 + --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380 + --initial-advertise-peer-urls=http://etcd1:2380 + --initial-cluster-state=new + + etcd2: + image: ghcr.io/zalando/spilo-16:3.2-p2 + networks: + - infisical + environment: + ETCD_UNSUPPORTED_ARCH: arm64 + container_name: demo-etcd2 + hostname: etcd2 + deploy: + placement: + constraints: + - node.labels.name == node2 + command: | + etcd --name etcd2 + --listen-client-urls http://0.0.0.0:2379 + --listen-peer-urls=http://0.0.0.0:2380 + --advertise-client-urls http://etcd2:2379 + --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380 + --initial-advertise-peer-urls=http://etcd2:2380 + --initial-cluster-state=new + + etcd3: + image: ghcr.io/zalando/spilo-16:3.2-p2 + networks: + - infisical + environment: + ETCD_UNSUPPORTED_ARCH: arm64 + container_name: demo-etcd3 + hostname: etcd3 + deploy: + placement: + constraints: + - node.labels.name == node3 + command: | + etcd --name etcd3 + --listen-client-urls http://0.0.0.0:2379 + --listen-peer-urls=http://0.0.0.0:2380 + --advertise-client-urls http://etcd3:2379 + --initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380 + --initial-advertise-peer-urls=http://etcd3:2380 + --initial-cluster-state=new + + spolo1: + image: ghcr.io/zalando/spilo-16:3.2-p2 + container_name: postgres-1 + networks: + - infisical + hostname: postgres-1 + environment: + ETCD_HOSTS: etcd1:2379,etcd2:2379,etcd3:2379 + SCOPE: infisical + volumes: + - postgres_data1:/home/postgres/pgdata + deploy: + placement: + constraints: + - node.labels.name == node1 + + spolo2: + image: ghcr.io/zalando/spilo-16:3.2-p2 + container_name: postgres-2 + networks: + - infisical + hostname: postgres-2 + environment: + ETCD_HOSTS: etcd1:2379,etcd2:2379,etcd3:2379 + SCOPE: infisical + volumes: + - postgres_data2:/home/postgres/pgdata + deploy: + placement: + constraints: + - node.labels.name == node2 + + spolo3: + image: ghcr.io/zalando/spilo-16:3.2-p2 + container_name: postgres-3 + networks: + - infisical + hostname: postgres-3 + environment: + ETCD_HOSTS: etcd1:2379,etcd2:2379,etcd3:2379 + SCOPE: infisical + volumes: + - postgres_data3:/home/postgres/pgdata + deploy: + placement: + constraints: + - node.labels.name == node3 + + + redis_replica0: + image: bitnami/redis:6.2.10 + environment: + - REDIS_REPLICATION_MODE=master + - REDIS_PASSWORD=123456 + networks: + - infisical + deploy: + placement: + constraints: + - node.labels.name == node1 + + redis_replica1: + image: bitnami/redis:6.2.10 + environment: + - REDIS_REPLICATION_MODE=slave + - REDIS_MASTER_HOST=redis_replica0 + - REDIS_MASTER_PORT_NUMBER=6379 + - REDIS_MASTER_PASSWORD=123456 + - REDIS_PASSWORD=123456 + networks: + - infisical + deploy: + placement: + constraints: + - node.labels.name == node2 + + redis_replica2: + image: bitnami/redis:6.2.10 + environment: + - REDIS_REPLICATION_MODE=slave + - REDIS_MASTER_HOST=redis_replica0 + - REDIS_MASTER_PORT_NUMBER=6379 + - REDIS_MASTER_PASSWORD=123456 + - REDIS_PASSWORD=123456 + networks: + - infisical + deploy: + placement: + constraints: + - node.labels.name == node3 + + redis_sentinel1: + image: bitnami/redis-sentinel:6.2.10 + environment: + - REDIS_SENTINEL_QUORUM=2 + - REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=5000 + - REDIS_SENTINEL_FAILOVER_TIMEOUT=60000 + - REDIS_SENTINEL_PORT_NUMBER=26379 + - REDIS_MASTER_HOST=redis_replica1 + - REDIS_MASTER_PORT_NUMBER=6379 + - REDIS_MASTER_PASSWORD=123456 + networks: + - infisical + deploy: + placement: + constraints: + - node.labels.name == node1 + + redis_sentinel2: + image: bitnami/redis-sentinel:6.2.10 + environment: + - REDIS_SENTINEL_QUORUM=2 + - REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=5000 + - REDIS_SENTINEL_FAILOVER_TIMEOUT=60000 + - REDIS_SENTINEL_PORT_NUMBER=26379 + - REDIS_MASTER_HOST=redis_replica1 + - REDIS_MASTER_PORT_NUMBER=6379 + - REDIS_MASTER_PASSWORD=123456 + networks: + - infisical + deploy: + placement: + constraints: + - node.labels.name == node2 + + redis_sentinel3: + image: bitnami/redis-sentinel:6.2.10 + environment: + - REDIS_SENTINEL_QUORUM=2 + - REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=5000 + - REDIS_SENTINEL_FAILOVER_TIMEOUT=60000 + - REDIS_SENTINEL_PORT_NUMBER=26379 + - REDIS_MASTER_HOST=redis_replica1 + - REDIS_MASTER_PORT_NUMBER=6379 + - REDIS_MASTER_PASSWORD=123456 + networks: + - infisical + deploy: + placement: + constraints: + - node.labels.name == node3 + +networks: + infisical: + + +volumes: + postgres_data1: + postgres_data2: + postgres_data3: + postgres_data4: + redis0: + redis1: + redis2: + +configs: + haproxy-config: + file: ./haproxy.cfg + +secrets: + env_file: + file: .env \ No newline at end of file diff --git a/docs/documentation/platform/dynamic-secrets/overview.mdx b/docs/documentation/platform/dynamic-secrets/overview.mdx index 42bc33223a..84477f1c23 100644 --- a/docs/documentation/platform/dynamic-secrets/overview.mdx +++ b/docs/documentation/platform/dynamic-secrets/overview.mdx @@ -1,13 +1,14 @@ --- -title: "Overview" +title: "Dynamic Secrets" +sidebarTitle: "Overview" description: "Learn how to generate secrets dynamically on-demand." --- ## Introduction -Contrary to static key-value secrets, which require manual input of data into the secure Infisical storage, dynamic secrets are generated on-demand upon access. +Contrary to static key-value secrets, which require manual input of data into the secure Infisical storage, **dynamic secrets are generated on-demand upon access**. -Dynamic secrets are unique to every identity using them. Such secrets come are generated only at the moment they are retrieved, eliminating the possibility of theft or reuse by another identity. Thanks to Infisical's integrated revocation capabilities, dynamic secrets can be promptly invalidated post-use, significantly reducing their lifespan. +**Dynamic secrets are unique to every identity using them**. Such secrets come are generated only at the moment they are retrieved, eliminating the possibility of theft or reuse by another identity. Thanks to Infisical's integrated revocation capabilities, dynamic secrets can be promptly invalidated post-use, significantly reducing their lifespan. ## Benefits of Dynamic Secrets @@ -28,3 +29,6 @@ Dynamic secrets are particularly useful in environments with stringent security ## Infisical Dynamic Secret Templates 1. [PostgreSQL](./postgresql) +2. [MySQL](./mysql) +3. [Cassandra](./cassandra) +4. [Oracle](./oracle) diff --git a/docs/images/integrations/aws/integrations-aws-parameter-store-auth.png b/docs/images/integrations/aws/integrations-aws-parameter-store-auth.png index 7891e636b1..14d54c9a6c 100644 Binary files a/docs/images/integrations/aws/integrations-aws-parameter-store-auth.png and b/docs/images/integrations/aws/integrations-aws-parameter-store-auth.png differ diff --git a/docs/images/integrations/aws/integrations-aws-parameter-store-create.png b/docs/images/integrations/aws/integrations-aws-parameter-store-create.png index 7c51eb2b2e..a168925d6e 100644 Binary files a/docs/images/integrations/aws/integrations-aws-parameter-store-create.png and b/docs/images/integrations/aws/integrations-aws-parameter-store-create.png differ diff --git a/docs/integrations/cloud/aws-parameter-store.mdx b/docs/integrations/cloud/aws-parameter-store.mdx index c872e39f0c..fdad8b6384 100644 --- a/docs/integrations/cloud/aws-parameter-store.mdx +++ b/docs/integrations/cloud/aws-parameter-store.mdx @@ -30,13 +30,18 @@ Prerequisites: "ssm:DeleteParameter", "ssm:GetParametersByPath", "ssm:DeleteParameters", - "ssm:AddTagsToResource" // if you need to add tags to secrets + "ssm:AddTagsToResource", // if you need to add tags to secrets + "kms:ListKeys", // if you need to specify the KMS key + "kms:ListAliases", // if you need to specify the KMS key + "kms:Encrypt", // if you need to specify the KMS key + "kms:Decrypt" // if you need to specify the KMS key ], "Resource": "*" } ] } ``` + Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys @@ -44,7 +49,7 @@ Prerequisites: ![access key 1](../../images/integrations/aws/integrations-aws-access-key-1.png) ![access key 2](../../images/integrations/aws/integrations-aws-access-key-2.png) ![access key 3](../../images/integrations/aws/integrations-aws-access-key-3.png) - + Navigate to your project's integrations tab in Infisical. ![integrations](../../images/integrations.png) @@ -59,6 +64,7 @@ Prerequisites: breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform. + Select which Infisical environment secrets you want to sync to which AWS Parameter Store region and indicate the path for your secrets. Then, press create integration to start syncing secrets to AWS Parameter Store. @@ -72,6 +78,6 @@ Prerequisites: secret like `TEST` to be stored as `/[project_name]/[environment]/TEST` in AWS Parameter Store. + - diff --git a/frontend/public/images/integrations/Agent.png b/frontend/public/images/integrations/Agent.png new file mode 100644 index 0000000000..662c1b2734 Binary files /dev/null and b/frontend/public/images/integrations/Agent.png differ diff --git a/frontend/public/images/integrations/Ansible.png b/frontend/public/images/integrations/Ansible.png new file mode 100644 index 0000000000..925e48ca6f Binary files /dev/null and b/frontend/public/images/integrations/Ansible.png differ diff --git a/frontend/public/images/integrations/ECS.png b/frontend/public/images/integrations/ECS.png new file mode 100644 index 0000000000..92e0d23ca0 Binary files /dev/null and b/frontend/public/images/integrations/ECS.png differ diff --git a/frontend/public/images/integrations/Jenkins.png b/frontend/public/images/integrations/Jenkins.png new file mode 100644 index 0000000000..9df069a185 Binary files /dev/null and b/frontend/public/images/integrations/Jenkins.png differ diff --git a/frontend/public/json/frameworkIntegrations.json b/frontend/public/json/frameworkIntegrations.json index 1a6ff94f29..c96c0a0b40 100644 --- a/frontend/public/json/frameworkIntegrations.json +++ b/frontend/public/json/frameworkIntegrations.json @@ -1,28 +1,4 @@ [ - { - "name": "Docker", - "slug": "docker", - "image": "Docker", - "docsLink": "https://infisical.com/docs/integrations/platforms/docker" - }, - { - "name": "Docker Compose", - "slug": "docker-compose", - "image": "Docker Compose", - "docsLink": "https://infisical.com/docs/integrations/platforms/docker-compose" - }, - { - "name": "Kubernetes", - "slug": "kubernetes", - "image": "Kubernetes", - "docsLink": "https://infisical.com/docs/integrations/platforms/kubernetes" - }, - { - "name": "Terraform", - "slug": "terraform", - "image": "Terraform", - "docsLink": "https://infisical.com/docs/integrations/frameworks/terraform" - }, { "name": "React", "slug": "react", diff --git a/frontend/public/json/infrastructureIntegrations.json b/frontend/public/json/infrastructureIntegrations.json new file mode 100644 index 0000000000..657cd17eb0 --- /dev/null +++ b/frontend/public/json/infrastructureIntegrations.json @@ -0,0 +1,50 @@ +[ + { + "name": "Docker", + "slug": "docker", + "image": "Docker", + "docsLink": "https://infisical.com/docs/integrations/platforms/docker" + }, + { + "name": "Docker Compose", + "slug": "docker-compose", + "image": "Docker Compose", + "docsLink": "https://infisical.com/docs/integrations/platforms/docker-compose" + }, + { + "name": "Kubernetes", + "slug": "kubernetes", + "image": "Kubernetes", + "docsLink": "https://infisical.com/docs/integrations/platforms/kubernetes" + }, + { + "name": "Terraform", + "slug": "terraform", + "image": "Terraform", + "docsLink": "https://infisical.com/docs/integrations/frameworks/terraform" + }, + { + "name": "Jenkins", + "slug": "jenkins", + "image": "Jenkins", + "docsLink": "https://infisical.com/docs/integrations/cicd/jenkins" + }, + { + "name": "Infisical Agent", + "slug": "agent", + "image": "Agent", + "docsLink": "https://infisical.com/docs/integrations/platforms/infisical-agent" + }, + { + "name": "Amazon ECS", + "slug": "ecs", + "image": "ECS", + "docsLink": "https://infisical.com/docs/integrations/platforms/ecs-with-agent" + }, + { + "name": "Ansible", + "slug": "ansible", + "image": "Ansible", + "docsLink": "https://infisical.com/docs/integrations/platforms/ansible" + } +] \ No newline at end of file diff --git a/frontend/public/locales/en/translations.json b/frontend/public/locales/en/translations.json index 8b04872923..b1465f04da 100644 --- a/frontend/public/locales/en/translations.json +++ b/frontend/public/locales/en/translations.json @@ -120,7 +120,7 @@ "available": "Platform & Cloud Integrations", "available-text1": "Click on the integration you want to connect. This will let your environment variables flow automatically into selected third-party services.", "available-text2": "Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. The core Infisical service will always stay end-to-end encrypted. With any questions, reach out support@infisical.com.", - "cloud-integrations": "Cloud Integrations", + "cloud-integrations": "Native Integrations", "framework-integrations": "Framework Integrations", "click-to-start": "Click on an integration to begin syncing secrets to it.", "click-to-setup": "Click on a framework to get the setup instructions.", diff --git a/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx b/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx index 2added8783..29f7ec3c84 100644 --- a/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx +++ b/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx @@ -353,7 +353,7 @@ export const InfisicalSecretInput = ({ highlightedIndex === i ? "bg-gray-600" : "" } text-md relative mb-0.5 flex w-full cursor-pointer select-none items-center justify-between rounded-md px-2 py-2 outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-500`} > -
+
, "size" | "onChange"> & { + value?: string | null; + isImport?: boolean; + isVisible?: boolean; + isReadOnly?: boolean; + isDisabled?: boolean; + environment?: string; + containerClassName?: string; + onChange?: (arg: string) => void; +}; + +export const SecretPathInput = ({ + containerClassName, + onChange, + environment, + value: propValue, + ...props +}: Props) => { + const [inputValue, setInputValue] = useState(propValue ?? ""); + const [secretPath, setSecretPath] = useState("/"); + const [suggestions, setSuggestions] = useState([]); + const [highlightedIndex, setHighlightedIndex] = useState(-1); + const debouncedInputValue = useDebounce(inputValue, 200); + + const { currentWorkspace } = useWorkspace(); + const workspaceId = currentWorkspace?.id || ""; + const { folderNames: folders } = useGetFoldersByEnv({ + path: secretPath, + environments: [environment || currentWorkspace?.environments?.[0].slug!], + projectId: workspaceId + }); + + useEffect(() => { + setInputValue(propValue ?? "/"); + }, [propValue]); + + useEffect(() => { + if (environment) { + setInputValue("/"); + setSecretPath("/"); + onChange?.("/"); + } + }, [environment]); + + useEffect(() => { + // update secret path if input is valid + if ( + (debouncedInputValue.length > 0 && + debouncedInputValue[debouncedInputValue.length - 1] === "/") || + debouncedInputValue.length === 0 + ) { + setSecretPath(debouncedInputValue); + } + + // filter suggestions based on matching + const searchFragment = debouncedInputValue.split("/").pop() || ""; + const filteredSuggestions = folders + .filter((suggestionEntry) => + suggestionEntry.toUpperCase().startsWith(searchFragment.toUpperCase()) + ) + .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); + + setSuggestions(filteredSuggestions); + }, [debouncedInputValue]); + + const handleSuggestionSelect = (selectedIndex: number) => { + if (!suggestions[selectedIndex]) { + return; + } + + const validPaths = inputValue.split("/"); + validPaths.pop(); + + const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}`; + onChange?.(newValue); + setInputValue(newValue); + setSecretPath(newValue); + setHighlightedIndex(-1); + setSuggestions([]); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + const mod = (n: number, m: number) => ((n % m) + m) % m; + if (e.key === "ArrowDown") { + setHighlightedIndex((prevIndex) => mod(prevIndex + 1, suggestions.length)); + } else if (e.key === "ArrowUp") { + setHighlightedIndex((prevIndex) => mod(prevIndex - 1, suggestions.length)); + } else if (e.key === "Enter" && highlightedIndex >= 0) { + handleSuggestionSelect(highlightedIndex); + } + if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) { + e.preventDefault(); + } + }; + + const handleInputChange = (e: any) => { + // propagate event to react-hook-form onChange + if (onChange) { + onChange(e.target.value); + } + + setInputValue(e.target.value); + }; + + return ( + 0 && inputValue.length > 1} + onOpenChange={() => { + setHighlightedIndex(-1); + }} + > + + + + e.preventDefault()} + className={twMerge( + "relative top-2 z-[100] overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 font-inter text-bunker-100 shadow-md" + )} + style={{ + width: "var(--radix-popover-trigger-width)", + maxHeight: "var(--radix-select-content-available-height)" + }} + > +
+ {suggestions.map((suggestion, i) => ( +
{ + e.preventDefault(); + setHighlightedIndex(i); + handleSuggestionSelect(i); + }} + style={{ pointerEvents: "auto" }} + className="flex items-center justify-between border-mineshaft-600 text-left" + key={`secret-reference-secret-${i + 1}`} + > +
+
+
+ +
+
{suggestion}
+
+
+
+ ))} +
+
+
+ ); +}; diff --git a/frontend/src/components/v2/SecretPathInput/index.tsx b/frontend/src/components/v2/SecretPathInput/index.tsx new file mode 100644 index 0000000000..5d8595891c --- /dev/null +++ b/frontend/src/components/v2/SecretPathInput/index.tsx @@ -0,0 +1 @@ +export { SecretPathInput } from "./SecretPathInput"; diff --git a/frontend/src/pages/integrations/[id].tsx b/frontend/src/pages/integrations/[id].tsx index 143e3ad2fe..8cc1baca35 100644 --- a/frontend/src/pages/integrations/[id].tsx +++ b/frontend/src/pages/integrations/[id].tsx @@ -1,14 +1,16 @@ import { useTranslation } from "react-i18next"; import Head from "next/head"; import frameworkIntegrationOptions from "public/json/frameworkIntegrations.json"; +import infrastructureIntegrationOptions from "public/json/infrastructureIntegrations.json"; import { IntegrationsPage } from "@app/views/IntegrationsPage"; type Props = { frameworkIntegrations: typeof frameworkIntegrationOptions; + infrastructureIntegrations: typeof infrastructureIntegrationOptions; }; -const Integration = ({ frameworkIntegrations }: Props) => { +const Integration = ({ frameworkIntegrations, infrastructureIntegrations }: Props) => { const { t } = useTranslation(); return ( @@ -20,7 +22,7 @@ const Integration = ({ frameworkIntegrations }: Props) => { - + ); }; @@ -28,7 +30,8 @@ const Integration = ({ frameworkIntegrations }: Props) => { export const getStaticProps = () => { return { props: { - frameworkIntegrations: frameworkIntegrationOptions + frameworkIntegrations: frameworkIntegrationOptions, + infrastructureIntegrations: infrastructureIntegrationOptions } }; }; diff --git a/frontend/src/pages/integrations/aws-parameter-store/create.tsx b/frontend/src/pages/integrations/aws-parameter-store/create.tsx index 92e4662a90..9f52347ce2 100644 --- a/frontend/src/pages/integrations/aws-parameter-store/create.tsx +++ b/frontend/src/pages/integrations/aws-parameter-store/create.tsx @@ -14,6 +14,7 @@ import { motion } from "framer-motion"; import queryString from "query-string"; import { useCreateIntegration } from "@app/hooks/api"; +import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries"; import { Button, @@ -90,6 +91,7 @@ export default function AWSParameterStoreCreateIntegrationPage() { const [shouldTag, setShouldTag] = useState(false); const [tagKey, setTagKey] = useState(""); const [tagValue, setTagValue] = useState(""); + const [kmsKeyId, setKmsKeyId] = useState(""); useEffect(() => { if (workspace) { @@ -98,6 +100,19 @@ export default function AWSParameterStoreCreateIntegrationPage() { } }, [workspace]); + + const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } = + useGetIntegrationAuthAwsKmsKeys({ + integrationAuthId: String(integrationAuthId), + region: selectedAWSRegion + }); + + useEffect(() => { + if (integrationAuthAwsKmsKeys) { + setKmsKeyId(String(integrationAuthAwsKmsKeys?.filter(key => key.alias === "default")[0]?.id)) + } + }, [integrationAuthAwsKmsKeys]) + const isValidAWSParameterStorePath = (awsStorePath: string) => { const pattern = /^\/([\w-]+\/)*[\w-]+\/$/; return pattern.test(awsStorePath) && awsStorePath.length <= 2048; @@ -133,7 +148,11 @@ export default function AWSParameterStoreCreateIntegrationPage() { value: tagValue }] } - : {}) + : {}), + ...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "default") ? + { + kmsKeyId + }: {}) } }); @@ -146,7 +165,7 @@ export default function AWSParameterStoreCreateIntegrationPage() { } }; - return integrationAuth && workspace && selectedSourceEnvironment ? ( + return (integrationAuth && workspace && selectedSourceEnvironment && !isIntegrationAuthAwsKmsKeysLoading) ? (
Set Up AWS Parameter Integration @@ -286,6 +305,31 @@ export default function AWSParameterStoreCreateIntegrationPage() {
)} + + + @@ -318,7 +362,7 @@ export default function AWSParameterStoreCreateIntegrationPage() { Set Up AWS Parameter Store Integration - {isintegrationAuthLoading ? ( + {(isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading) ? ( key.id === kmsKeyId)[0]?.alias !== "alias/aws/secretsmanager") ? + ...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "default") ? { kmsKeyId }: {}) diff --git a/frontend/src/pages/integrations/cloudflare-pages/create.tsx b/frontend/src/pages/integrations/cloudflare-pages/create.tsx index c2eb1c61a4..570b2b83a0 100644 --- a/frontend/src/pages/integrations/cloudflare-pages/create.tsx +++ b/frontend/src/pages/integrations/cloudflare-pages/create.tsx @@ -4,16 +4,10 @@ import axios from "axios"; import queryString from "query-string"; import { createNotification } from "@app/components/notifications"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useCreateIntegration, useGetWorkspaceById } from "@app/hooks/api"; -import { - Button, - Card, - CardTitle, - FormControl, - Input, - Select, - SelectItem} from "../../../components/v2"; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2"; import { useGetIntegrationAuthApps, useGetIntegrationAuthById @@ -27,7 +21,6 @@ const cloudflareEnvironments = [ export default function CloudflarePagesIntegrationPage() { const router = useRouter(); const { mutateAsync } = useCreateIntegration(); - const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); const [secretPath, setSecretPath] = useState("/"); @@ -130,9 +123,10 @@ export default function CloudflarePagesIntegrationPage() { - setSecretPath(evt.target.value)} + onChange={(value) => setSecretPath(value)} + environment={selectedSourceEnvironment} placeholder="Provide a path, default is /" /> diff --git a/frontend/src/pages/integrations/cloudflare-workers/create.tsx b/frontend/src/pages/integrations/cloudflare-workers/create.tsx index 43808e98d3..f4a35d47c6 100644 --- a/frontend/src/pages/integrations/cloudflare-workers/create.tsx +++ b/frontend/src/pages/integrations/cloudflare-workers/create.tsx @@ -4,17 +4,10 @@ import axios from "axios"; import queryString from "query-string"; import { createNotification } from "@app/components/notifications"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useCreateIntegration, useGetWorkspaceById } from "@app/hooks/api"; -import { - Button, - Card, - CardTitle, - FormControl, - Input, - Select, - SelectItem -} from "../../../components/v2"; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2"; import { useGetIntegrationAuthApps, useGetIntegrationAuthById @@ -23,7 +16,6 @@ import { export default function CloudflareWorkersIntegrationPage() { const router = useRouter(); const { mutateAsync } = useCreateIntegration(); - const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? ""); @@ -122,9 +114,10 @@ export default function CloudflareWorkersIntegrationPage() { - setSecretPath(evt.target.value)} + onChange={(value) => setSecretPath(value)} + environment={selectedSourceEnvironment} placeholder="Provide a path, default is /" /> diff --git a/frontend/src/pages/integrations/gcp-secret-manager/create.tsx b/frontend/src/pages/integrations/gcp-secret-manager/create.tsx index 497f023703..12dcb6ca8f 100644 --- a/frontend/src/pages/integrations/gcp-secret-manager/create.tsx +++ b/frontend/src/pages/integrations/gcp-secret-manager/create.tsx @@ -11,6 +11,7 @@ import { motion } from "framer-motion"; import queryString from "query-string"; import * as yup from "yup"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { usePopUp } from "@app/hooks"; import { useCreateIntegration } from "@app/hooks/api"; @@ -258,7 +259,11 @@ export default function GCPSecretManagerCreateIntegrationPage() { isError={Boolean(error)} errorText={error?.message} > - + )} /> diff --git a/frontend/src/pages/integrations/gitlab/create.tsx b/frontend/src/pages/integrations/gitlab/create.tsx index 183040341b..15daad2706 100644 --- a/frontend/src/pages/integrations/gitlab/create.tsx +++ b/frontend/src/pages/integrations/gitlab/create.tsx @@ -11,6 +11,7 @@ import { motion } from "framer-motion"; import queryString from "query-string"; import * as yup from "yup"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { usePopUp } from "@app/hooks"; import { useCreateIntegration } from "@app/hooks/api"; @@ -268,7 +269,11 @@ export default function GitLabCreateIntegrationPage() { isError={Boolean(error)} errorText={error?.message} > - + )} /> diff --git a/frontend/src/pages/integrations/hasura-cloud/create.tsx b/frontend/src/pages/integrations/hasura-cloud/create.tsx index 6f8eac255c..476a6a8f56 100644 --- a/frontend/src/pages/integrations/hasura-cloud/create.tsx +++ b/frontend/src/pages/integrations/hasura-cloud/create.tsx @@ -9,15 +9,8 @@ import { yupResolver } from "@hookform/resolvers/yup"; import queryString from "query-string"; import * as yup from "yup"; -import { - Button, - Card, - CardTitle, - FormControl, - Input, - Select, - SelectItem -} from "@app/components/v2"; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthApps, @@ -38,6 +31,7 @@ export default function HasuraCloudCreateIntegrationPage() { const { control, handleSubmit, + watch, formState: { isSubmitting } } = useForm({ resolver: yupResolver(schema) @@ -51,6 +45,8 @@ export default function HasuraCloudCreateIntegrationPage() { (integrationAuthId as string) ?? "" ); + const selectedSourceEnvironment = watch("sourceEnvironment"); + const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({ integrationAuthId: (integrationAuthId as string) ?? "" @@ -147,7 +143,7 @@ export default function HasuraCloudCreateIntegrationPage() { name="secretPath" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/pages/integrations/heroku/create.tsx b/frontend/src/pages/integrations/heroku/create.tsx index db22372531..4d25b72600 100644 --- a/frontend/src/pages/integrations/heroku/create.tsx +++ b/frontend/src/pages/integrations/heroku/create.tsx @@ -17,19 +17,12 @@ import queryString from "query-string"; // import { App, Pipeline } from "@app/hooks/api/integrationAuth/types"; import * as yup from "yup"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; // import { RadioGroup } from "@app/components/v2/RadioGroup"; import { useCreateIntegration } from "@app/hooks/api"; import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; -import { - Button, - Card, - CardTitle, - FormControl, - Input, - Select, - SelectItem -} from "../../../components/v2"; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2"; import { useGetIntegrationAuthApps, useGetIntegrationAuthById @@ -280,7 +273,11 @@ export default function HerokuCreateIntegrationPage() { name="secretPath" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/pages/integrations/render/create.tsx b/frontend/src/pages/integrations/render/create.tsx index d911940ac2..e52f3668e6 100644 --- a/frontend/src/pages/integrations/render/create.tsx +++ b/frontend/src/pages/integrations/render/create.tsx @@ -15,6 +15,7 @@ import { yupResolver } from "@hookform/resolvers/yup"; import queryString from "query-string"; import * as yup from "yup"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useCreateIntegration } from "@app/hooks/api"; import { @@ -22,7 +23,6 @@ import { Card, CardTitle, FormControl, - Input, Select, SelectItem, Switch @@ -185,7 +185,11 @@ export default function RenderCreateIntegrationPage() { name="secretPath" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx b/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx index 9f51ac1375..73c443ca40 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationsPage.tsx @@ -21,15 +21,17 @@ import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { CloudIntegrationSection } from "./components/CloudIntegrationSection"; import { FrameworkIntegrationSection } from "./components/FrameworkIntegrationSection"; +import { InfrastructureIntegrationSection } from "./components/InfrastructureIntegrationSection/InfrastructureIntegrationSection"; import { IntegrationsSection } from "./components/IntegrationsSection"; import { generateBotKey, redirectForProviderAuth } from "./IntegrationPage.utils"; type Props = { frameworkIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>; + infrastructureIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>; }; export const IntegrationsPage = withProjectPermission( - ({ frameworkIntegrations }: Props) => { + ({ frameworkIntegrations, infrastructureIntegrations }: Props) => { const { t } = useTranslation(); @@ -228,6 +230,7 @@ export const IntegrationsPage = withProjectPermission( +
); }, diff --git a/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx b/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx index f81f8befe0..1644702898 100644 --- a/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/CloudIntegrationSection/CloudIntegrationSection.tsx @@ -109,7 +109,7 @@ export const CloudIntegrationSection = ({
{cloudIntegration.isAvailable && Boolean(integrationAuths?.[cloudIntegration.slug]) && ( -
+
diff --git a/frontend/src/views/IntegrationsPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx b/frontend/src/views/IntegrationsPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx index d77b976bc6..3b1df9bdd6 100644 --- a/frontend/src/views/IntegrationsPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx @@ -1,4 +1,7 @@ import { useTranslation } from "react-i18next"; +import { faKeyboard } from "@fortawesome/free-regular-svg-icons"; +import { faComputer } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; type Props = { frameworks: Array<{ @@ -50,6 +53,36 @@ export const FrameworkIntegrationSection = ({ frameworks }: Props) => {
))} + +
+ +
+ CLI +
+
+ + ); diff --git a/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/InfrastructureIntegrationSection.tsx b/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/InfrastructureIntegrationSection.tsx new file mode 100644 index 0000000000..08652ff66f --- /dev/null +++ b/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/InfrastructureIntegrationSection.tsx @@ -0,0 +1,50 @@ +type Props = { + integrations: Array<{ + name: string; + image: string; + slug: string; + docsLink: string; + }>; +}; + +export const InfrastructureIntegrationSection = ({ integrations }: Props) => { + const sortedIntegrations = integrations.sort((a, b) => a.name.localeCompare(b.name)); + + return ( + <> +
+

Infrastructure Integrations

+

Click on of the integration to read the documentation.

+
+ + + ); +}; diff --git a/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/index.tsx b/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/index.tsx new file mode 100644 index 0000000000..8ca5a43c79 --- /dev/null +++ b/frontend/src/views/IntegrationsPage/components/InfrastructureIntegrationSection/index.tsx @@ -0,0 +1 @@ +export { InfrastructureIntegrationSection } from "./InfrastructureIntegrationSection"; diff --git a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx index 2944203d08..185cc411fb 100644 --- a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx @@ -137,7 +137,7 @@ export const IntegrationsSection = ({ "App" } /> -
+
{(integration.integration === "hashicorp-vault" && `${integration.app} - path: ${integration.path}`) || (integration.scope === "github-org" && `${integration.owner}`) || @@ -217,11 +217,12 @@ export const IntegrationsSection = ({ isOpen={popUp.deleteConfirmation.isOpen} title={`Are you sure want to remove ${ (popUp?.deleteConfirmation.data as TIntegration)?.integration || " " - } integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || " "}?`} + } integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || "this project"}?`} onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)} deleteKey={ (popUp?.deleteConfirmation?.data as TIntegration)?.app || (popUp?.deleteConfirmation?.data as TIntegration)?.owner || + (popUp?.deleteConfirmation?.data as TIntegration)?.path || "" } onDeleteApproved={async () => diff --git a/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx b/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx index 83a6b6e455..56248145b3 100644 --- a/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx +++ b/frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx @@ -34,6 +34,7 @@ import { Tag, Tooltip } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ProjectPermissionActions, ProjectPermissionSub, @@ -115,6 +116,7 @@ const SpecificPrivilegeSecretForm = ({ }); const temporaryAccessField = privilegeForm.watch("temporaryAccess"); + const selectedEnvironmentSlug = privilegeForm.watch("environmentSlug"); const isTemporary = temporaryAccessField?.isTemporary; const isExpired = temporaryAccessField.isTemporary && @@ -220,7 +222,12 @@ const SpecificPrivilegeSecretForm = ({ name="secretPath" render={({ field }) => ( - + )} /> diff --git a/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx b/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx index 2df959a94d..97321de7ce 100644 --- a/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx +++ b/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx @@ -34,6 +34,7 @@ import { Tag, Tooltip } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ProjectPermissionActions, ProjectPermissionSub, @@ -107,6 +108,7 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri }); const temporaryAccessField = privilegeForm.watch("temporaryAccess"); + const selectedEnvironmentSlug = privilegeForm.watch("environmentSlug"); const isTemporary = temporaryAccessField?.isTemporary; const isExpired = temporaryAccessField.isTemporary && @@ -208,7 +210,12 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri name="secretPath" render={({ field }) => ( - + )} /> diff --git a/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretPolicyForm.tsx b/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretPolicyForm.tsx index eb9d5303ad..185c596c26 100644 --- a/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretPolicyForm.tsx +++ b/frontend/src/views/SecretApprovalPage/components/SecretApprovalPolicyList/components/SecretPolicyForm.tsx @@ -20,6 +20,7 @@ import { Select, SelectItem } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useWorkspace } from "@app/context"; import { useCreateSecretApprovalPolicy, useUpdateSecretApprovalPolicy } from "@app/hooks/api"; import { TSecretApprovalPolicy } from "@app/hooks/api/types"; @@ -59,13 +60,14 @@ export const SecretPolicyForm = ({ control, handleSubmit, reset, + watch, formState: { isSubmitting } } = useForm({ resolver: zodResolver(formSchema), values: editValues ? { ...editValues, environment: editValues.environment.slug } : undefined }); const { currentWorkspace } = useWorkspace(); - + const selectedEnvironment = watch("environment"); const environments = currentWorkspace?.environments || []; useEffect(() => { @@ -174,7 +176,11 @@ export const SecretPolicyForm = ({ name="secretPath" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx index fa8340465e..a6dfe7c0e3 100644 --- a/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx +++ b/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx @@ -4,15 +4,8 @@ import { AxiosError } from "axios"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { - Button, - FormControl, - Input, - Modal, - ModalContent, - Select, - SelectItem -} from "@app/components/v2"; +import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useWorkspace } from "@app/context"; import { useCreateSecretImport } from "@app/hooks/api"; @@ -50,12 +43,12 @@ export const CreateSecretImportForm = ({ handleSubmit, control, reset, + watch, formState: { isSubmitting } } = useForm({ resolver: zodResolver(typeSchema) }); const { currentWorkspace } = useWorkspace(); const environments = currentWorkspace?.environments || []; - - + const selectedEnvironment = watch("environment"); const { mutateAsync: createSecretImport } = useCreateSecretImport(); @@ -130,7 +123,7 @@ export const CreateSecretImportForm = ({ defaultValue="/" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx b/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx index 574b8da946..1d83f591b5 100644 --- a/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx +++ b/frontend/src/views/SecretMainPage/components/SecretDropzone/CopySecretsFromBoard.tsx @@ -28,6 +28,7 @@ import { Skeleton, Tooltip } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { useDebounce } from "@app/hooks"; import { useGetProjectSecrets } from "@app/hooks/api"; @@ -76,7 +77,6 @@ export const CopySecretsFromBoard = ({ handleSubmit, control, watch, - register, reset, setValue, formState: { isDirty } @@ -192,9 +192,19 @@ export const CopySecretsFromBoard = ({ )} /> - - - + ( + + + + )} + />
diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx index 4b6449a337..3ddacf5b4d 100644 --- a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx @@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, FormControl, Input, Select, SelectItem, Spinner } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useWorkspace } from "@app/context"; import { useGetProjectSecrets, useGetUserWsKey } from "@app/hooks/api"; @@ -78,7 +79,7 @@ export const RotationOutputForm = ({ onSubmit, onCancel, outputSchema = {} }: Pr defaultValue="/" render={({ field }) => ( - + )} />