Compare commits

...

139 Commits

Author SHA1 Message Date
Maidul Islam
c8c5caba62 Update Chart.yaml 2024-03-25 13:48:17 -04:00
Maidul Islam
f408a6f60c Update values.yaml 2024-03-25 13:48:01 -04:00
Maidul Islam
391ed0ed74 Update build-staging-and-deploy-aws.yml 2024-03-25 11:15:39 -04:00
Daniel Hougaard
aef40212d2 Merge pull request #1528 from rhythmbhiwani/cli-fix-update-vars
Fixed CLI issue of updating variables using `infisical secrets set`
2024-03-25 15:30:47 +01:00
Akhil Mohan
5aa7cd46c1 Merge pull request #1594 from Salman2301/feat-cloudflare-secret-path
feat: add support for secret path for cloudflare page
2024-03-25 11:37:00 +05:30
Maidul Islam
6c0b916ad8 set version to short commit sha 2024-03-25 01:54:53 -04:00
Akhil Mohan
d7bc80308d Merge pull request #1566 from Salman2301/fix-typo-input
fix class name typo
2024-03-25 11:14:55 +05:30
Akhil Mohan
b7c7b242e8 Merge pull request #1578 from Salman2301/fix-select-key-nav
fix: add highlighted style for select component
2024-03-25 11:13:48 +05:30
Maidul Islam
37827367ed Merge pull request #1622 from Infisical/daniel/mi-project-creation-bug
Fix: Creating projects with Machine Identities that aren't org admins
2024-03-24 14:46:23 -04:00
Maidul Islam
403b1ce993 Merge pull request #1620 from Infisical/daniel/e2ee-button
Feat: Deprecate E2EE mode switching
2024-03-24 14:33:44 -04:00
Daniel Hougaard
c3c0006a25 Update project-service.ts 2024-03-24 17:15:10 +01:00
Maidul Islam
2241908d0a fix lining for gha 2024-03-24 00:52:21 -04:00
Maidul Islam
59b822510c update job names gha 2024-03-24 00:50:40 -04:00
Maidul Islam
d1408aff35 update pipeline 2024-03-24 00:49:47 -04:00
Maidul Islam
c67084f08d combine migration job with deploy 2024-03-24 00:47:46 -04:00
Maidul Islam
a280e002ed add prod deploy 2024-03-24 00:35:10 -04:00
Maidul Islam
76c4a8660f Merge pull request #1621 from redcubie/patch-1
Move DB_CONNECTION_URI to make sure DB credentials are initialized
2024-03-23 12:59:27 -04:00
redcubie
8c54dd611e Move DB_CONNECTION_URI to make sure DB credentials are initialized 2024-03-23 18:49:15 +02:00
Maidul Islam
5c75f526e7 Update build-staging-and-deploy-aws.yml 2024-03-22 17:18:45 -04:00
Maidul Islam
113e777b25 add wait for ecs 2024-03-22 16:16:22 -04:00
Maidul Islam
2a93449ffe add needs[] for gamm deploy gha 2024-03-22 14:56:38 -04:00
Maidul Islam
1ef1c042da add back other build steps 2024-03-22 14:55:55 -04:00
Daniel Hougaard
b64672a921 Update E2EESection.tsx 2024-03-22 19:33:53 +01:00
Daniel Hougaard
227e013502 Feat: Deprecate E2EE mode switching 2024-03-22 19:31:41 +01:00
Maidul Islam
44ca8c315e remove all stages except deploy in gha 2024-03-22 14:12:13 -04:00
Daniel Hougaard
7766a7f4dd Merge pull request #1619 from Infisical/daniel/mi-ux-fix
Update IdentityModal.tsx
2024-03-22 18:56:07 +01:00
Daniel Hougaard
3cb150a749 Update IdentityModal.tsx 2024-03-22 18:27:57 +01:00
Maidul Islam
9e9ce261c8 give gha permission to update git token 2024-03-22 12:59:51 -04:00
Maidul Islam
fab7167850 update oidc audience 2024-03-22 12:54:27 -04:00
Akhil Mohan
c7de9aab4e Merge pull request #1618 from Infisical/gha-aws-pipeline
deploy to ecs using OIDC with aws
2024-03-22 22:13:09 +05:30
Maidul Islam
3560346f85 update step name 2024-03-22 12:42:32 -04:00
Maidul Islam
f0bf2f8dd0 seperate aws rds uri 2024-03-22 12:38:10 -04:00
Maidul Islam
2a6216b8fc deploy to ecs using OIDC with aws 2024-03-22 12:29:07 -04:00
Akhil Mohan
c05230f667 Merge pull request #1616 from Infisical/wait-for-job-helm
Update Chart.yaml
2024-03-22 19:03:32 +05:30
Maidul Islam
d68055a264 Update Chart.yaml
Update to multi arch and rootless
2024-03-22 09:28:44 -04:00
Maidul Islam
dc6056b564 Merge pull request #1614 from francodalmau/fix-environment-popups-cancel-action
Fix add and update environment popups cancel button
2024-03-21 21:28:28 -04:00
franco_dalmau
94f0811661 Fix add and update environment popups cancel button 2024-03-21 20:09:57 -03:00
Maidul Islam
7b84ae6173 Update Chart.yaml 2024-03-21 15:08:38 -04:00
Maidul Islam
5710a304f8 Merge pull request #1533 from Infisical/daniel/k8-operator-machine-identities
Feat: K8 Operator Machine Identity Support
2024-03-21 15:01:50 -04:00
Daniel Hougaard
91e3bbba34 Fix: Requested changes 2024-03-21 19:58:10 +01:00
Daniel Hougaard
02112ede07 Fix: Requested changes 2024-03-21 19:53:21 +01:00
Daniel Hougaard
08cfbf64e4 Fix: Error handing 2024-03-21 19:37:12 +01:00
Daniel Hougaard
18da522b45 Chore: Helm charts 2024-03-21 18:35:00 +01:00
Daniel Hougaard
8cf68fbd9c Generated 2024-03-21 17:12:42 +01:00
Daniel Hougaard
d6b82dfaa4 Fix: Rebase sample conflicts 2024-03-21 17:09:41 +01:00
Daniel Hougaard
7bd4eed328 Chore: Generate K8 helm charts 2024-03-21 17:08:01 +01:00
Daniel Hougaard
0341c32da0 Fix: Change credentials -> credentialsRef 2024-03-21 17:08:01 +01:00
Daniel Hougaard
caea055281 Feat: Improve K8 docs 2024-03-21 17:08:01 +01:00
Daniel Hougaard
c08c78de8d Feat: Rename universalAuthMachineIdentity to universalAuth 2024-03-21 17:08:01 +01:00
Daniel Hougaard
3765a14246 Fix: Generate new types 2024-03-21 17:08:01 +01:00
Daniel Hougaard
c5a11e839b Feat: Deprecate Service Accounts 2024-03-21 17:08:01 +01:00
Daniel Hougaard
93bd3d8270 Docs: Simplified docs more 2024-03-21 17:07:56 +01:00
Daniel Hougaard
b9601dd418 Update kubernetes.mdx 2024-03-21 17:07:56 +01:00
Daniel Hougaard
ae3bc04b07 Docs 2024-03-21 17:07:31 +01:00
Daniel Hougaard
11edefa66f Feat: Added project slug support 2024-03-21 17:07:31 +01:00
Daniel Hougaard
f71459ede0 Slugs 2024-03-21 17:07:31 +01:00
Daniel Hougaard
33324a5a3c Type generation 2024-03-21 17:07:31 +01:00
Daniel Hougaard
5c6781a705 Update machine-identity-token.go 2024-03-21 17:07:31 +01:00
Daniel Hougaard
71e31518d7 Feat: Add machine identity token handler 2024-03-21 17:07:31 +01:00
Daniel Hougaard
f6f6db2898 Fix: Moved update attributes type to models 2024-03-21 17:07:31 +01:00
Daniel Hougaard
55780b65d3 Feat: Machine Identity support (token refreshing logic) 2024-03-21 17:07:31 +01:00
Daniel Hougaard
83bbf9599d Feat: Machine Identity support 2024-03-21 17:07:31 +01:00
Daniel Hougaard
f8f2b2574d Feat: Machine Identity support (types) 2024-03-21 17:07:31 +01:00
Daniel Hougaard
318d12addd Feat: Machine Identity support 2024-03-21 17:07:31 +01:00
Daniel Hougaard
872a28d02a Feat: Machine Identity support for K8 2024-03-21 17:06:49 +01:00
Daniel Hougaard
6f53a5631c Fix: Double prints 2024-03-21 17:06:49 +01:00
Daniel Hougaard
ff2098408d Update sample.yaml 2024-03-21 17:06:48 +01:00
Daniel Hougaard
9e85d9bbf0 Example 2024-03-21 17:05:46 +01:00
Daniel Hougaard
0f3a48bb32 Generated 2024-03-21 17:05:46 +01:00
Daniel Hougaard
f869def8ea Added new types 2024-03-21 17:05:46 +01:00
Maidul Islam
378bc57a88 Merge pull request #1480 from akhilmhdh/docs/rotation-doc-update
docs: improved secret rotation documentation with better understanding
2024-03-21 11:17:19 -04:00
Maidul Islam
242179598b fix types, rephrase, and revise rotation docs 2024-03-21 11:03:41 -04:00
Maidul Islam
e3e049b66c Update build-staging-and-deploy.yml 2024-03-20 22:14:46 -04:00
Maidul Islam
878e4a79e7 Merge pull request #1606 from Infisical/daniel/ui-imported-folders-fix
Fix: UI indicator for imports
2024-03-20 22:08:50 -04:00
Daniel Hougaard
609ce8e5cc Fix: Improved UI import indicators 2024-03-21 03:06:14 +01:00
Maidul Islam
04c1ea9b11 Update build-staging-and-deploy.yml 2024-03-20 18:03:49 -04:00
Maidul Islam
3baca73e53 add seperate step for ecr build 2024-03-20 16:25:54 -04:00
Daniel Hougaard
36adf6863b Fix: UI secret import indicator 2024-03-20 21:09:54 +01:00
Daniel Hougaard
6363e7d30a Update index.ts 2024-03-20 20:26:28 +01:00
Daniel Hougaard
f9621fad8e Fix: Remove duplicate type 2024-03-20 20:26:00 +01:00
Daniel Hougaard
90be28b87a Feat: Import indicator 2024-03-20 20:25:48 +01:00
Daniel Hougaard
671adee4d7 Feat: Indicator for wether or not secrets are imported 2024-03-20 20:24:06 +01:00
Daniel Hougaard
c9cb90c98e Feat: Add center property to tooltip 2024-03-20 20:23:21 +01:00
Akhil Mohan
9f691df395 Merge pull request #1607 from Infisical/fix-secrets-by-name
set imports=true for get secret by name
2024-03-20 23:50:15 +05:30
Maidul Islam
d702a61586 set imports=true for get secret by name 2024-03-20 14:06:16 -04:00
Maidul Islam
1c16f406a7 remove debug 2024-03-20 13:06:29 -04:00
Maidul Islam
90f739caa6 correct repo name env 2024-03-20 13:05:59 -04:00
Maidul Islam
ede8b6f286 add .env context for ecr tag 2024-03-20 13:00:06 -04:00
Maidul Islam
232c547d75 correct ecr image tag 2024-03-20 11:54:33 -04:00
Maidul Islam
fe08bbb691 push to ecr 2024-03-20 11:46:47 -04:00
Maidul Islam
2bd06ecde4 login into ecr 2024-03-20 11:31:39 -04:00
Daniel Hougaard
08b79d65ea Fix: Remove unused lint disable 2024-03-20 14:55:02 +01:00
Daniel Hougaard
4e1733ba6c Fix: More reverting 2024-03-20 14:44:40 +01:00
Daniel Hougaard
a4e495ea1c Fix: Restructured frontend 2024-03-20 14:42:34 +01:00
Daniel Hougaard
a750d68363 Fix: Reverted backend changes 2024-03-20 14:40:24 +01:00
Daniel Hougaard
d7161a353d Fix: Better variable naming 2024-03-20 13:15:51 +01:00
Daniel Hougaard
12c414817f Fix: Remove debugging logs 2024-03-20 13:14:18 +01:00
Daniel Hougaard
e5e494d0ee Fix: Also display imported folder indicator for nested folders 2024-03-20 13:13:50 +01:00
Daniel Hougaard
5a21b85e9e Fix: Removed overlap from other working branch 2024-03-20 13:13:19 +01:00
Daniel Hougaard
348fdf6429 Feat: Visualize imported folders in overview page (include imported folders in response) 2024-03-20 12:56:11 +01:00
Daniel Hougaard
88e609cb66 Feat: New types for imported folders 2024-03-20 12:55:40 +01:00
Daniel Hougaard
78058d691a Enhancement: Add disabled prop to Tooltip component 2024-03-20 12:55:08 +01:00
Daniel Hougaard
1d465a50c3 Feat: Visualize imported folders in overview page 2024-03-20 12:54:44 +01:00
Maidul Islam
ffc7249c7c update diagram 2024-03-19 23:44:12 -04:00
Maidul Islam
90bcf23097 Update README.md 2024-03-19 23:36:07 -04:00
Maidul Islam
5fa4d9029d Merge pull request #1577 from Salman2301/fix-notification-z-index
fix: notification error behind detail sidebar
2024-03-19 18:56:14 -04:00
Maidul Islam
7160cf58ee Merge branch 'main' into fix-notification-z-index 2024-03-19 18:50:58 -04:00
Maidul Islam
6b2d757e39 remove outdated healthcheck 2024-03-19 17:21:46 -04:00
Daniel Hougaard
c075fcceca Merge pull request #1591 from Infisical/daniel/prettier-fix
Chore: Prettier formatting
2024-03-19 21:23:11 +01:00
Maidul Islam
e25f5dd65f Merge pull request #1605 from Infisical/creation-policy-k8s
add managed secret creation policy
2024-03-19 15:23:16 -04:00
Maidul Islam
3eef023c30 add managed secret creation policy 2024-03-19 14:58:17 -04:00
Tuan Dang
e63deb0860 Patch org role slug validation 2024-03-19 10:23:00 -07:00
Maidul Islam
02b2851990 Merge pull request #1601 from Infisical/fix/db-host
fix(server): updated secret rotation to pick on db host in validation
2024-03-19 10:03:12 -04:00
Akhil Mohan
cb828200e1 fix(server): updated secret rotation to pick on db host in validation 2024-03-19 13:56:21 +00:00
Akhil Mohan
77d068ae2c Merge pull request #1599 from Infisical/daniel/improve-create-project
Fix: Remove required org slug from create project route
2024-03-19 18:31:16 +05:30
Daniel Hougaard
8702af671d Fix: Typings error 2024-03-19 13:45:57 +01:00
Daniel Hougaard
31c0fd96ea Update UserInfoStep.tsx 2024-03-19 13:39:34 +01:00
Daniel Hougaard
2c539697df Feat: Remove orgSlug from create project endpoint 2024-03-19 13:39:24 +01:00
Daniel Hougaard
ae97b74933 Feat: Improve create project, remove organization slug from frontend 2024-03-19 13:38:58 +01:00
Akhil Mohan
3e6af2dae5 Merge pull request #1597 from Infisical/daniel/api-endpoint-fix
Fix: Mintlify interactive API docs defaulting to localhost server
2024-03-19 16:32:49 +05:30
Daniel Hougaard
3c91e1127f Fix: Mintlify docs defaulting to localhost endpoint 2024-03-19 11:58:01 +01:00
vmatsiiako
0e31a9146a Update ee.mdx 2024-03-18 22:10:09 -07:00
Salman
d2a93eb1d2 feat: add support for secret path for cloudflare page 2024-03-18 21:31:21 +05:30
Daniel Hougaard
fa1b28b33f Update .eslintrc.js 2024-03-18 16:07:49 +01:00
Daniel Hougaard
415cf31b2d Fix: Lint bug (Cannot read properties of undefined (reading 'getTokens')) 2024-03-18 16:01:14 +01:00
Daniel Hougaard
9002e6cb33 Fix: Format entire frontend properly 2024-03-18 16:00:03 +01:00
Daniel Hougaard
1ede551c3e Fix: Format entire frontend properly 2024-03-18 15:59:47 +01:00
Daniel Hougaard
b7b43858f6 Fix: Format entire frontend properly 2024-03-18 15:55:01 +01:00
Akhil Mohan
c91789e6d0 Merge pull request #1590 from Infisical/daniel/ts-comments
Fix: Github warnings / Lint warnings
2024-03-18 20:19:26 +05:30
Daniel Hougaard
db0ba4be10 Fix: Github warnings / Lint warnings 2024-03-18 15:47:03 +01:00
Akhil Mohan
f73c807aa0 Merge pull request #1589 from Infisical/daniel/ui-improvements
Fix: Select organization UX & project card enhancements
2024-03-18 20:14:39 +05:30
Salman
203e00216f fix: add highlighted style for select component 2024-03-16 17:53:48 +05:30
Salman
ee215bccfa fix: notification error behind detail sidebar 2024-03-16 08:34:21 +05:30
Salman
7a3a6663f1 fix class name typo 2024-03-14 03:37:54 +05:30
Akhil Mohan
8c491668dc docs: updated images of inputs in secret rotation 2024-03-07 23:17:32 +05:30
Akhil Mohan
c873e2cba8 docs: updated secret rotation doc with images 2024-03-05 15:42:53 +05:30
Maidul Islam
1bc045a7fa update overview sendgrid 2024-03-05 15:42:53 +05:30
Akhil Mohan
533de93199 docs: improved secret rotation documentation with better understanding 2024-03-05 15:42:53 +05:30
Rhythm Bhiwani
115b4664bf Fixed CLI issue of updating variables using infisical secrets set 2024-03-05 03:43:06 +05:30
344 changed files with 5139 additions and 4141 deletions

View File

@@ -3,9 +3,6 @@
# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION # THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
# Required
DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
# JWT # JWT
# Required secrets to sign JWT tokens # Required secrets to sign JWT tokens
# THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION # THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION
@@ -16,6 +13,9 @@ POSTGRES_PASSWORD=infisical
POSTGRES_USER=infisical POSTGRES_USER=infisical
POSTGRES_DB=infisical POSTGRES_DB=infisical
# Required
DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
# Redis # Redis
REDIS_URL=redis://redis:6379 REDIS_URL=redis://redis:6379

View File

@@ -0,0 +1,149 @@
name: Deployment pipeline
on: [workflow_dispatch]
permissions:
id-token: write
contents: read
jobs:
infisical-image:
name: Build backend image
runs-on: ubuntu-latest
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: 📦 Install dependencies to test all dependencies
run: npm ci --only-production
working-directory: backend
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Depot CLI
uses: depot/setup-action@v1
- name: 📦 Build backend and export to Docker
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
load: true
context: .
file: Dockerfile.standalone-infisical
tags: infisical/infisical:test
- name: 🏗️ Build backend and push to docker hub
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true
context: .
file: Dockerfile.standalone-infisical
tags: |
infisical/staging_infisical:${{ steps.commit.outputs.short }}
infisical/staging_infisical:latest
platforms: linux/amd64,linux/arm64
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
INFISICAL_PLATFORM_VERSION=${{ steps.commit.outputs.short }}
gamma-deployment:
name: Deploy to gamma
runs-on: ubuntu-latest
needs: [infisical-image]
environment:
name: Gamma
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node.js environment
uses: actions/setup-node@v2
with:
node-version: "20"
- name: Change directory to backend and install dependencies
env:
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
run: |
cd backend
npm install
npm run migration:latest
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
audience: sts.amazonaws.com
aws-region: us-east-1
role-to-assume: arn:aws:iam::905418227878:role/deploy-new-ecs-img
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition infisical-prod-platform --query taskDefinition > task-definition.json
- name: Render Amazon ECS task definition
id: render-web-container
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: infisical-prod-platform
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-prod-platform
cluster: infisical-prod-platform
wait-for-service-stability: true
production-postgres-migration:
name: Deploy to production
runs-on: ubuntu-latest
needs: [gamma-deployment]
environment:
name: Production
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node.js environment
uses: actions/setup-node@v2
with:
node-version: "20"
- name: Change directory to backend and install dependencies
env:
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
run: |
cd backend
npm install
npm run migration:latest
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
audience: sts.amazonaws.com
aws-region: us-east-1
role-to-assume: arn:aws:iam::905418227878:role/deploy-new-ecs-img
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition infisical-prod-platform --query taskDefinition > task-definition.json
- name: Render Amazon ECS task definition
id: render-web-container
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: infisical-prod-platform
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-prod-platform
cluster: infisical-prod-platform
wait-for-service-stability: true

View File

@@ -8,6 +8,15 @@ jobs:
steps: steps:
- name: ☁️ Checkout source - name: ☁️ Checkout source
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_FOR_ECR }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_FOR_ECR }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: 📦 Install dependencies to test all dependencies - name: 📦 Install dependencies to test all dependencies
run: npm ci --only-production run: npm ci --only-production
working-directory: backend working-directory: backend
@@ -35,16 +44,7 @@ jobs:
context: . context: .
file: Dockerfile.standalone-infisical file: Dockerfile.standalone-infisical
tags: infisical/infisical:test tags: infisical/infisical:test
# - name: ⏻ Spawn backend container and dependencies - name: 🏗️ Build backend and push to docker hub
# run: |
# docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
# - name: 🧪 Test backend image
# run: |
# ./.github/resources/healthcheck.sh infisical-backend-test
# - name: ⏻ Shut down backend container and dependencies
# run: |
# docker compose -f .github/resources/docker-compose.be-test.yml down
- name: 🏗️ Build backend and push
uses: depot/build-push-action@v1 uses: depot/build-push-action@v1
with: with:
project: 64mmf0n610 project: 64mmf0n610
@@ -59,6 +59,8 @@ jobs:
build-args: | build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }} INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
postgres-migration: postgres-migration:
name: Run latest migration files name: Run latest migration files
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -118,9 +118,6 @@ WORKDIR /backend
ENV TELEMETRY_ENABLED true ENV TELEMETRY_ENABLED true
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node healthcheck.js
EXPOSE 8080 EXPOSE 8080
EXPOSE 443 EXPOSE 443

View File

@@ -10,7 +10,8 @@
<a href="https://infisical.com/">Infisical Cloud</a> | <a href="https://infisical.com/">Infisical Cloud</a> |
<a href="https://infisical.com/docs/self-hosting/overview">Self-Hosting</a> | <a href="https://infisical.com/docs/self-hosting/overview">Self-Hosting</a> |
<a href="https://infisical.com/docs/documentation/getting-started/introduction">Docs</a> | <a href="https://infisical.com/docs/documentation/getting-started/introduction">Docs</a> |
<a href="https://www.infisical.com">Website</a> <a href="https://www.infisical.com">Website</a> |
<a href="https://infisical.com/careers">Hiring (Remote/SF)</a>
</h4> </h4>
<p align="center"> <p align="center">

View File

@@ -19,7 +19,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
.min(1) .min(1)
.trim() .trim()
.refine( .refine(
(val) => Object.keys(OrgMembershipRole).includes(val), (val) => !Object.keys(OrgMembershipRole).includes(val),
"Please choose a different slug, the slug you have entered is reserved" "Please choose a different slug, the slug you have entered is reserved"
) )
.refine((v) => slugify(v) === v, { .refine((v) => slugify(v) === v, {

View File

@@ -9,6 +9,7 @@ import jmespath from "jmespath";
import knex from "knex"; import knex from "knex";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { getDbConnectionHost } from "@app/lib/knex";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TAssignOp, TDbProviderClients, TDirectAssignOp, THttpProviderFunction } from "../templates/types"; import { TAssignOp, TDbProviderClients, TDirectAssignOp, THttpProviderFunction } from "../templates/types";
@@ -89,7 +90,7 @@ export const secretRotationDbFn = async ({
const appCfg = getConfig(); const appCfg = getConfig();
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined; const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
if (host === "localhost" || host === "127.0.0.1" || appCfg.DB_CONNECTION_URI.includes(host)) if (host === "localhost" || host === "127.0.0.1" || getDbConnectionHost(appCfg.DB_CONNECTION_URI) === host)
throw new Error("Invalid db host"); throw new Error("Invalid db host");
const db = knex({ const db = knex({

View File

@@ -0,0 +1,11 @@
import { URL } from "url"; // Import the URL class
export const getDbConnectionHost = (urlString: string) => {
try {
const url = new URL(urlString);
// Split hostname and port (if provided)
return url.hostname.split(":")[0];
} catch (error) {
return null;
}
};

View File

@@ -4,6 +4,7 @@ import { Tables } from "knex/types/tables";
import { DatabaseError } from "../errors"; import { DatabaseError } from "../errors";
export * from "./connection";
export * from "./join"; export * from "./join";
export * from "./select"; export * from "./select";

View File

@@ -14,13 +14,13 @@ export const fastifySwagger = fp(async (fastify) => {
version: "0.0.1" version: "0.0.1"
}, },
servers: [ servers: [
{
url: "http://localhost:8080",
description: "Local server"
},
{ {
url: "https://app.infisical.com", url: "https://app.infisical.com",
description: "Production server" description: "Production server"
},
{
url: "http://localhost:8080",
description: "Local server"
} }
], ],
components: { components: {

View File

@@ -150,8 +150,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
message: "Slug must be a valid slug" message: "Slug must be a valid slug"
}) })
.optional() .optional()
.describe(PROJECTS.CREATE.slug), .describe(PROJECTS.CREATE.slug)
organizationSlug: z.string().trim().describe(PROJECTS.CREATE.organizationSlug)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -166,7 +165,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
orgSlug: req.body.organizationSlug,
workspaceName: req.body.projectName, workspaceName: req.body.projectName,
slug: req.body.slug slug: req.body.slug
}); });

View File

@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { ProjectMembershipRole, ProjectVersion } from "@app/db/schemas"; import { OrgMembershipRole, ProjectMembershipRole, ProjectVersion } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
@@ -92,7 +92,6 @@ export const projectServiceFactory = ({
* Create workspace. Make user the admin * Create workspace. Make user the admin
* */ * */
const createProject = async ({ const createProject = async ({
orgSlug,
actor, actor,
actorId, actorId,
actorOrgId, actorOrgId,
@@ -100,13 +99,7 @@ export const projectServiceFactory = ({
workspaceName, workspaceName,
slug: projectSlug slug: projectSlug
}: TCreateProjectDTO) => { }: TCreateProjectDTO) => {
if (!orgSlug) { const organization = await orgDAL.findOne({ id: actorOrgId });
throw new BadRequestError({
message: "Must provide organization slug to create project"
});
}
const organization = await orgDAL.findOne({ slug: orgSlug });
const { permission, membership: orgMembership } = await permissionService.getOrgPermission( const { permission, membership: orgMembership } = await permissionService.getOrgPermission(
actor, actor,
@@ -291,10 +284,11 @@ export const projectServiceFactory = ({
// Get the role permission for the identity // Get the role permission for the identity
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole( const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
ProjectMembershipRole.Admin, OrgMembershipRole.Member,
organization.id organization.id
); );
// Identity has to be at least a member in order to create projects
const hasPrivilege = isAtLeastAsPrivileged(permission, rolePermission); const hasPrivilege = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPrivilege) if (!hasPrivilege)
throw new ForbiddenRequestError({ throw new ForbiddenRequestError({

View File

@@ -24,7 +24,6 @@ export type TCreateProjectDTO = {
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorId: string; actorId: string;
actorOrgId?: string; actorOrgId?: string;
orgSlug: string;
workspaceName: string; workspaceName: string;
slug?: string; slug?: string;
}; };

View File

@@ -232,6 +232,7 @@ export const secretFolderServiceFactory = ({
if (!parentFolder) return []; if (!parentFolder) return [];
const folders = await folderDAL.find({ envId: env.id, parentId: parentFolder.id }); const folders = await folderDAL.find({ envId: env.id, parentId: parentFolder.id });
return folders; return folders;
}; };

View File

@@ -406,14 +406,14 @@ func CallDeleteSecretsV3(httpClient *resty.Client, request DeleteSecretV3Request
return nil return nil
} }
func CallUpdateSecretsV3(httpClient *resty.Client, request UpdateSecretByNameV3Request) error { func CallUpdateSecretsV3(httpClient *resty.Client, request UpdateSecretByNameV3Request, secretName string) error {
var secretsResponse GetEncryptedSecretsV3Response var secretsResponse GetEncryptedSecretsV3Response
response, err := httpClient. response, err := httpClient.
R(). R().
SetResult(&secretsResponse). SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT). SetHeader("User-Agent", USER_AGENT).
SetBody(request). SetBody(request).
Patch(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName)) Patch(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, secretName))
if err != nil { if err != nil {
return fmt.Errorf("CallUpdateSecretsV3: Unable to complete api request [err=%s]", err) return fmt.Errorf("CallUpdateSecretsV3: Unable to complete api request [err=%s]", err)

View File

@@ -401,7 +401,6 @@ type DeleteSecretV3Request struct {
} }
type UpdateSecretByNameV3Request struct { type UpdateSecretByNameV3Request struct {
SecretName string `json:"secretName"`
WorkspaceID string `json:"workspaceId"` WorkspaceID string `json:"workspaceId"`
Environment string `json:"environment"` Environment string `json:"environment"`
Type string `json:"type"` Type string `json:"type"`

View File

@@ -297,7 +297,6 @@ var secretsSetCmd = &cobra.Command{
updateSecretRequest := api.UpdateSecretByNameV3Request{ updateSecretRequest := api.UpdateSecretByNameV3Request{
WorkspaceID: workspaceFile.WorkspaceId, WorkspaceID: workspaceFile.WorkspaceId,
Environment: environmentName, Environment: environmentName,
SecretName: secret.PlainTextKey,
SecretValueCiphertext: secret.SecretValueCiphertext, SecretValueCiphertext: secret.SecretValueCiphertext,
SecretValueIV: secret.SecretValueIV, SecretValueIV: secret.SecretValueIV,
SecretValueTag: secret.SecretValueTag, SecretValueTag: secret.SecretValueTag,
@@ -305,7 +304,7 @@ var secretsSetCmd = &cobra.Command{
SecretPath: secretsPath, SecretPath: secretsPath,
} }
err = api.CallUpdateSecretsV3(httpClient, updateSecretRequest) err = api.CallUpdateSecretsV3(httpClient, updateSecretRequest, secret.PlainTextKey)
if err != nil { if err != nil {
util.HandleError(err, "Unable to process secret update request") util.HandleError(err, "Unable to process secret update request")
return return
@@ -419,7 +418,7 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
util.HandleError(err, "Unable to parse path flag") util.HandleError(err, "Unable to parse path flag")
} }
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}, "") secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: true}, "")
if err != nil { if err != nil {
util.HandleError(err, "To fetch all secrets") util.HandleError(err, "To fetch all secrets")
} }
@@ -477,7 +476,7 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
util.HandleError(err, "Unable to parse flag") util.HandleError(err, "Unable to parse flag")
} }
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}, "") secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: true}, "")
if err != nil { if err != nil {
util.HandleError(err, "To fetch all secrets") util.HandleError(err, "To fetch all secrets")
} }

View File

@@ -1,37 +1,102 @@
--- ---
title: "MySQL/MariaDB" title: "MySQL/MariaDB"
description: "Rotated database user password of a MySQL or MariaDB" description: "How to rotate MySQL/MariaDB database user passwords"
--- ---
Infisical will update periodically the provided database user's password. The Infisical MySQL secret rotation allows you to automatically rotate your MySQL database user's password at a predefined interval.
<Warning>
At present Infisical do require access to your database. We will soon be released Infisical agent based rotation which would help you rotate without direct database access from Infisical cloud.
</Warning>
## Working ## Prerequisite
1. User's has to create the two user's for Infisical to rotate and provide them required database access 1. Create two users with the required permission in your MySQL instance. We'll refer to them as `user-a` and `user-b`.
2. Infisical will connect with your database with admin access 2. Create another MySQL user with just the permission to update the passwords of `user-a` and `user-b`. We'll refer to this user as the `admin` user.
3. If last rotated one was username1, then username2 is chosen to be rotated
5. Update it's password with random value To learn more about MySQL permission system, please visit this [documentation](https://dev.mysql.com/doc/refman/8.0/en/privileges-provided.html).
6. After testing it gets saved to the provided secret mapping
## How it works
1. Infisical connects to your database using the provided `admin` user account.
2. A random value is generated and the password for `user-a` is updated with the new value.
3. The new password is then tested by logging into the database
4. If test is success, it's saved to the output secret mappings so that rest of the system gets the newly rotated value(s).
5. The process is then repeated for `user-b` on the next rotation.
6. The cycle repeats until secret rotation is deleted/stopped.
## Rotation Configuration ## Rotation Configuration
1. Head over to Secret Rotation configuration page of your project by clicking on side bar `Secret Rotation` <Steps>
2. Click on `MySQL` <Step title="Open Secret Rotation Page">
3. Provide the inputs Head over to Secret Rotation configuration page of your project by clicking on `Secret Rotation` in the left side bar
- Admin Username: DB admin username </Step>
- Admin Password: DB admin password <Step title="Click on MySQL card" />
- Host: DB host <Step title="Provide the inputs">
- Port: DB port(number) <ParamField path="Admin Username" type="string" required>
- Username1: The first username in two to rotate Rotator admin username
- Username2: The second username in two to rotate </ParamField>
- CA: Certificate to connect with database(string)
4. Final step
- Select `Environment`, `Secret Path` and `Interval` to rotate the secrets
- Finally select the secrets in your provided board to replace with new secret after each rotation
- Your done and good to go.
Congrats. You have 10x your MySQL/MariaDB access security. <ParamField path="Admin password" type="string" required>
Rotator admin password
</ParamField>
<ParamField path="Host" type="string" required>
Database host url
</ParamField>
<ParamField path="Port" type="number" required>
Database port number
</ParamField>
<ParamField path="Username1" type="string" required>
The first username of two to rotate - `user-a`
</ParamField>
<ParamField path="Username2" type="string" required>
The second username of two to rotate - `user-b`
</ParamField>
<ParamField path="CA" type="string">
Optional database certificate to connect with database
</ParamField>
</Step>
<Step title="Configure the output secret mapping">
When a secret rotation is successful, the updated values needs to be saved to an existing key(s) in your project.
<ParamField path="Environment" type="string" required>
The environment where the rotated credentials should be mapped to.
</ParamField>
<ParamField path="Secret Path" type="string" required>
The secret path where the rotated credentials should be mapped to.
</ParamField>
<ParamField path="Interval" type="number" required>
What interval should the credentials be rotated in days.
</ParamField>
<ParamField path="DB USERNAME" type="string" required>
Select an existing secret key where the rotated database username value should be saved to.
</ParamField>
<ParamField path="DB PASSWORD" type="string" required>
Select an existing select key where the rotated database password value should be saved to.
</ParamField>
</Step>
</Steps>
## FAQ
<AccordionGroup>
<Accordion title="Why can't we delete the other user when rotating?">
When a system has multiple nodes by horizontal scaling, redeployment doesn't happen instantly.
This means that when the secrets are rotated, and the redeployment is triggered, the existing system will still be using the old credentials until the change rolls out.
To avoid causing failure for them, the old credentials are not removed. Instead, in the next rotation, the previous user's credentials are updated.
</Accordion>
<Accordion title="Why do you need root user account?">
The admin account is used by Infisical to update the credentials for `user-a` and `user-b`.
You don't need to grant all permission for your admin account but rather just the permissions to update both of the user's passwords.
</Accordion>
</AccordionGroup>

View File

@@ -1,33 +1,104 @@
--- ---
title: "PostgreSQL/CockroachDB" title: "PostgreSQL/CockroachDB"
description: "Rotated database user password of a PostgreSQL or Cockroach DB" description: "How to rotate postgreSQL/cockroach database user passwords"
--- ---
Infisical will update periodically the provided database user's password. The Infisical Postgres secret rotation allows you to automatically rotate your Postgres database user's password at a predefined interval.
## Working
1. User's has to create the two user's for Infisical to rotate and provide them required database access. ## Prerequisite
2. Infisical will connect with your database with admin access.
3. If last rotated one was username1, then username2 is chosen to be rotated. 1. Create two users with the required permission in your PostgreSQL instance. We'll refer to them as `user-a` and `user-b`.
5. Update it's password with random value. 2. Create another PostgreSQL user with just the permission to update the passwords of `user-a` and `user-b`. We'll refer to this user as the `admin` user.
6. After testing it gets saved to the provided secret mapping.
To learn more about Postgres permission system, please visit this [documentation](https://www.postgresql.org/docs/9.1/sql-grant.html).
## How it works
1. Infisical connects to your database using the provided `admin` user account.
2. A random value is generated and the password for `user-a` is updated with the new value.
3. The new password is then tested by logging into the database
4. If test is success, it's saved to the output secret mappings so that rest of the system gets the newly rotated value(s).
5. The process is then repeated for `user-b` on the next rotation.
6. The cycle repeats until secret rotation is deleted/stopped.
## Rotation Configuration ## Rotation Configuration
1. Head over to Secret Rotation configuration page of your project by clicking on side bar `Secret Rotation` <Steps>
2. Click on `PostgreSQL` <Step title="Open Secret Rotation Page">
3. Provide the inputs Head over to Secret Rotation configuration page of your project by clicking on `Secret Rotation` in the left side bar
- Admin Username: DB admin username </Step>
- Admin Password: DB admin password <Step title="Click on PostgresSQL card" />
- Host: DB host
- Port: DB port(number)
- Username1: The first username in two to rotate
- Username2: The second username in two to rotate
- CA: Certificate to connect with database(string)
4. Final step
- Select `Environment`, `Secret Path` and `Interval` to rotate the secrets
- Finally select the secrets in your provided board to replace with new secret after each rotation
- Your done and good to go.
Congratulations. You have improved your PostgreSQL/CockroachDB access security. <Step title="Provide the inputs">
<ParamField path="Admin Username" type="string" required="true">
Rotator admin username
</ParamField>
<ParamField path="Admin password" type="string" required="true">
Rotator admin password
</ParamField>
<ParamField path="Host" type="string" required="true">
Database host url
</ParamField>
<ParamField path="Port" type="number" required="true">
Database port number
</ParamField>
<ParamField path="Username1" type="string" required="true">
The first username of two to rotate - `user-a`
</ParamField>
<ParamField path="Username2" type="string" required="true">
The second username of two to rotate - `user-b`
</ParamField>
<ParamField path="CA" type="string" optional>
Optional database certificate to connect with database
</ParamField>
</Step>
<Step title="Configure the output secret mapping">
When a secret rotation is successful, the updated values needs to be saved to an existing key(s) in your project.
<ParamField path="Environment" type="string" required>
The environment where the rotated credentials should be mapped to.
</ParamField>
<ParamField path="Secret Path" type="string" required>
The secret path where the rotated credentials should be mapped to.
</ParamField>
<ParamField path="Interval" type="number" required>
What interval should the credentials be rotated in days.
</ParamField>
<ParamField path="DB USERNAME" type="string" required>
Select an existing secret key where the rotated database username value should be saved to.
</ParamField>
<ParamField path="DB PASSWORD" type="string" required>
Select an existing select key where the rotated database password value should be saved to.
</ParamField>
</Step>
</Steps>
## FAQ
<AccordionGroup>
<Accordion title="Why can't we delete the other user when rotating?">
When a system has multiple nodes by horizontal scaling, redeployment doesn't happen instantly.
This means that when the secrets are rotated, and the redeployment is triggered, the existing system will still be using the old credentials until the change rolls out.
To avoid causing failure for them, the old credentials are not removed. Instead, in the next rotation, the previous user's credentials are updated.
</Accordion>
<Accordion title="Why do you need root user account?">
The admin account is used by Infisical to update the credentials for `user-a` and `user-b`.
You don't need to grant all permission for your admin account but rather just the permissions to update both of the user's passwords.
</Accordion>
</AccordionGroup>

View File

@@ -1,31 +1,58 @@
--- ---
title: "Twilio SendGrid" title: "Twilio SendGrid"
description: "Rotate Twilio SendGrid API keys" description: "How to rotate Twilio SendGrid API keys"
--- ---
Twilio SendGrid is a cloud-based email delivery platform that helps businesses send transactional and marketing emails. Eliminate the use of long lived secrets by rotating Twilio SendGrid API keys with Infisical.
It uses an API key to do various operations. Using Infisical you can easily dynamically change the keys.
## Working ## Prerequisite
1. Infisical will need an admin token of SendGrid to create API keys dynamically. You will need a valid SendGrid admin key with the necessary scope to create additional API keys.
2. Using the given admin token and scope by user Infisical will create and rotate API keys periodically
3. Under the hood infisical uses [SendGrid API](https://docs.sendgrid.com/api-reference/api-keys/create-api-keys) Follow the [SendGrid Docs to create an admin api key](https://docs.sendgrid.com/ui/account-and-settings/api-keys)
## How it works
Using the provided admin API key, Infisical will attempt to create child API keys with the specified permissions.
New keys will ge generated every time a rotation occurs. Behind the scenes, Infisical uses the [SendGrid API](https://docs.sendgrid.com/api-reference/api-keys/create-api-keys) to generate new API keys.
## Rotation Configuration ## Rotation Configuration
1. Head over to Secret Rotation configuration page of your project by clicking on side bar `Secret Rotation` <Steps>
2. Click on `Twilio SendGrid Card` <Step title="Open Secret Rotation Page">
3. Provide the inputs Head over to Secret Rotation configuration page of your project by clicking on `Secret Rotation` in the left side bar
- Admin API Key: </Step>
SendGrid admin key to create lower scoped API keys. <Step title="Click on Twilio SendGrid Card" />
- API Key Scopes <Step title="Provide the inputs">
SendGrid generated API Key's scopes. For more info refer [this doc](https://docs.sendgrid.com/api-reference/api-key-permissions/api-key-permissions) <ParamField path="Admin API Key" type="string" required>
SendGrid admin API key with permission to create child scoped API keys.
</ParamField>
4. Final step <ParamField path="Admin API Key" type="array" required>
- Select `Environment`, `Secret Path` and `Interval` to rotate the secrets The permissions that the newly generated API keys will have. To view possible permissions, visit [this documentation](https://docs.sendgrid.com/api-reference/api-key-permissions/api-key-permissions).
- Finally select the secrets in your provided board to replace with new secret after each rotation Permissions must be entered as a list of strings.
- Your done and good to go.
Example: `["user.profile.read", "user.profile.update"]`
</ParamField>
</Step>
<Step title="Configure the output secret mapping">
When a secret rotation is successful, the updated values needs to be saved to an existing key(s) in your project.
<ParamField path="Environment" type="string" required>
The environment where the rotated credentials should be mapped to.
</ParamField>
<ParamField path="Secret Path" type="string" required>
The secret path where the rotated credentials should be mapped to.
</ParamField>
<ParamField path="Interval" type="number" required>
What interval should the credentials be rotated in days.
</ParamField>
<ParamField path="API KEY" type="string" required>
Select an existing select key where the newly rotated API key will get saved to.
</ParamField>
</Step>
</Steps>
Now your output mapped secret value will be replaced periodically by SendGrid. Now your output mapped secret value will be replaced periodically by SendGrid.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 733 KiB

After

Width:  |  Height:  |  Size: 739 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -12,7 +12,7 @@ The operator continuously updates secrets and can also reload dependent deployme
## Install Operator ## Install Operator
The operator can be install via [Helm](helm.sh) or [kubectl](https://github.com/kubernetes/kubectl) The operator can be install via [Helm](https://helm.sh) or [kubectl](https://github.com/kubernetes/kubectl)
<Tabs> <Tabs>
<Tab title="Helm (recommended)"> <Tab title="Helm (recommended)">
@@ -61,23 +61,38 @@ Once you have installed the operator to your cluster, you'll need to create a `I
apiVersion: secrets.infisical.com/v1alpha1 apiVersion: secrets.infisical.com/v1alpha1
kind: InfisicalSecret kind: InfisicalSecret
metadata: metadata:
# Name of of this InfisicalSecret resource
name: infisicalsecret-sample name: infisicalsecret-sample
labels:
label-to-be-passed-to-managed-secret: sample-value
annotations:
example.com/annotation-to-be-passed-to-managed-secret: "sample-value"
spec: spec:
# The host that should be used to pull secrets from. If left empty, the value specified in Global configuration will be used
hostAPI: https://app.infisical.com/api hostAPI: https://app.infisical.com/api
resyncInterval: 60 resyncInterval: 10
authentication: authentication:
# Make sure to only have 1 authentication method defined, serviceToken/universalAuth.
# If you have multiple authentication methods defined, it may cause issues.
universalAuth:
secretsScope:
projectSlug: <project-slug>
envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/"
credentialsRef:
secretName: universal-auth-credentials
secretNamespace: default
serviceToken: serviceToken:
serviceTokenSecretReference: serviceTokenSecretReference:
secretName: service-token secretName: service-token
secretNamespace: default secretNamespace: default
secretsScope: secretsScope:
envSlug: dev envSlug: <env-slug>
secretsPath: "/" secretsPath: <secrets-path> # Root is "/"
managedSecretReference: managedSecretReference:
secretName: managed-secret # <-- the name of kubernetes secret that will be created secretName: managed-secret
secretNamespace: default # <-- where the kubernetes secret should be created secretNamespace: default
# secretType: kubernetes.io/dockerconfigjson
``` ```
### InfisicalSecret CRD properties ### InfisicalSecret CRD properties
@@ -105,11 +120,60 @@ Default re-sync interval is every 1 minute.
</Accordion> </Accordion>
<Accordion title="authentication"> <Accordion title="authentication">
This block defines the method that will be used to authenticate with Infisical so that secrets can be fetched. Currently, only [Service Tokens](../../documentation/platform/token) can be used to authenticate with Infisical. This block defines the method that will be used to authenticate with Infisical so that secrets can be fetched
</Accordion> </Accordion>
<Accordion title="authentication.serviceToken.serviceTokenSecretReference"> <Accordion title="authentication.universalAuth">
The service token required to authenticate with Infisical needs to be stored in a Kubernetes secret. This block defines the reference to the name and name space of secret that stores this service token. The universal machine identity authentication method is used to authenticate with Infisical. The client ID and client secret needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores these credentials.
<Steps>
<Step title="Create a machine identity">
You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about machine identities here](/documentation/platform/identities/universal-auth).
</Step>
<Step title="Create Kubernetes secret containing machine identity credentials">
Once you have created your machine identity and added it to your project(s), you will need to create a Kubernetes secret containing the identity credentials.
To quickly create a Kubernetes secret containing the identity credentials, you can run the command below.
Make sure you replace `<your-identity-client-id>` with the identity client ID and `<your-identity-client-secret>` with the identity client secret.
``` bash
kubectl create secret generic universal-auth-credentials --from-literal=clientId="<your-identity-client-id>" --from-literal=clientSecret="<your-identity-client-secret>"
```
</Step>
<Step title="Add reference for the Kubernetes secret containing the identity credentials">
Once the secret is created, add the `secretName` and `secretNamespace` of the secret that was just created under `authentication.universalAuth.credentialsRef` field in the InfisicalSecret resource.
</Step>
</Steps>
<Info>
Make sure to also populate the `secretsScope` field with the project slug _`projectSlug`_, environment slug _`envSlug`_, and secrets path _`secretsPath`_ that you want to fetch secrets from. Please see the example below.
</Info>
## Example
```yaml
apiVersion: secrets.infisical.com/v1alpha1
kind: InfisicalSecret
metadata:
name: infisicalsecret-sample-crd
spec:
authentication:
universalAuth:
secretsScope:
projectSlug: <project-slug> # <-- project slug
envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/"
credentialsRef:
secretName: universal-auth-credentials # <-- name of the Kubernetes secret that stores our machine identity credentials
secretNamespace: default # <-- namespace of the Kubernetes secret that stores our machine identity credentials
...
```
</Accordion>
<Accordion title="authentication.serviceToken">
The service token required to authenticate with Infisical needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores this service token.
Follow the instructions below to create and store the service token in a Kubernetes secrets and reference it in your CRD. Follow the instructions below to create and store the service token in a Kubernetes secrets and reference it in your CRD.
#### 1. Generate service token #### 1. Generate service token
@@ -122,13 +186,17 @@ Default re-sync interval is every 1 minute.
To quickly create a Kubernetes secret containing the generated service token, you can run the command below. Make sure you replace `<your-service-token-here>` with your service token. To quickly create a Kubernetes secret containing the generated service token, you can run the command below. Make sure you replace `<your-service-token-here>` with your service token.
``` bash ``` bash
kubectl create secret generic service-token --from-literal=infisicalToken=<your-service-token-here> kubectl create secret generic service-token --from-literal=infisicalToken="<your-service-token-here>"
``` ```
#### 3. Add reference for the Kubernetes secret containing service token #### 3. Add reference for the Kubernetes secret containing service token
Once the secret is created, add the name and namespace of the secret that was just created under `authentication.serviceToken.serviceTokenSecretReference` field in the InfisicalSecret resource. Once the secret is created, add the name and namespace of the secret that was just created under `authentication.serviceToken.serviceTokenSecretReference` field in the InfisicalSecret resource.
<Info>
Make sure to also populate the `secretsScope` field with the, environment slug _`envSlug`_, and secrets path _`secretsPath`_ that you want to fetch secrets from. Please see the example below.
</Info>
## Example ## Example
```yaml ```yaml
apiVersion: secrets.infisical.com/v1alpha1 apiVersion: secrets.infisical.com/v1alpha1
@@ -141,25 +209,13 @@ Default re-sync interval is every 1 minute.
serviceTokenSecretReference: serviceTokenSecretReference:
secretName: service-token # <-- name of the Kubernetes secret that stores our service token secretName: service-token # <-- name of the Kubernetes secret that stores our service token
secretNamespace: option # <-- namespace of the Kubernetes secret that stores our service token secretNamespace: option # <-- namespace of the Kubernetes secret that stores our service token
secretsScope:
envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: <secrets-path> # Root is "/"
... ...
``` ```
</Accordion> </Accordion>
<Accordion title="authentication.serviceToken.secretsScope">
This block defines the scope of what secrets should be fetched. This is needed as your service token can have access to multiple folders and environments.
A scope is defined by `envSlug` and `secretsPath`.
#### envSlug
This refers to the short hand name of an environment. For example for the `development` environment the environment slug is `dev`. You can locate the slug of your environment by heading to your project settings in the Infisical dashboard.
#### secretsPath
secretsPath is the path to the secret in the given environment. For example a path of `/` would refer to the root of the environment whereas `/folder1` would refer to the secrets in folder1 from the root.
Both fields are required.
</Accordion>
<Accordion title="managedSecretReference"> <Accordion title="managedSecretReference">
The `managedSecretReference` field is used to define the target location for storing secrets retrieved from an Infisical project. The `managedSecretReference` field is used to define the target location for storing secrets retrieved from an Infisical project.
This field requires specifying both the name and namespace of the Kubernetes secret that will hold these secrets. This field requires specifying both the name and namespace of the Kubernetes secret that will hold these secrets.

View File

@@ -9,7 +9,7 @@ This guide walks through how you can use these paid features in Infisical.
<Steps> <Steps>
<Step title="Purchase a license"> <Step title="Purchase a license">
Start by either signing up for a free demo [here](https://infisical.com/schedule-demo) or contacting team@infisical.com to purchase a license. Start by either signing up for a free demo [here](https://infisical.com/schedule-demo) or contacting sales@infisical.com to purchase a license.
Once purchased, you will be issued a license key. Once purchased, you will be issued a license key.
</Step> </Step>

View File

@@ -29,6 +29,7 @@ module.exports = {
}, },
plugins: ["react", "prettier", "simple-import-sort", "import"], plugins: ["react", "prettier", "simple-import-sort", "import"],
rules: { rules: {
"@typescript-eslint/no-empty-function": "off",
quotes: ["error", "double", { avoidEscape: true }], quotes: ["error", "double", { avoidEscape: true }],
"comma-dangle": ["error", "only-multiline"], "comma-dangle": ["error", "only-multiline"],
"react/react-in-jsx-scope": "off", "react/react-in-jsx-scope": "off",
@@ -72,7 +73,6 @@ module.exports = {
], ],
"@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-non-null-assertion": "off",
"simple-import-sort/exports": "warn", "simple-import-sort/exports": "warn",
"@typescript-eslint/no-empty-function": "off",
"simple-import-sort/imports": [ "simple-import-sort/imports": [
"warn", "warn",
{ {

View File

@@ -1,28 +1,28 @@
const path = require('path'); const path = require("path");
module.exports = { module.exports = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [ addons: [
'@storybook/addon-links', "@storybook/addon-links",
'@storybook/addon-essentials', "@storybook/addon-essentials",
'@storybook/addon-interactions', "@storybook/addon-interactions",
'storybook-dark-mode', "storybook-dark-mode",
{ {
name: '@storybook/addon-styling', name: "@storybook/addon-styling",
options: { options: {
postCss: { postCss: {
implementation: require('postcss') implementation: require("postcss")
} }
} }
} }
], ],
framework: { framework: {
name: '@storybook/nextjs', name: "@storybook/nextjs",
options: {} options: {}
}, },
core: { core: {
disableTelemetry: true disableTelemetry: true
}, },
docs: { docs: {
autodocs: 'tag' autodocs: "tag"
} }
}; };

View File

@@ -6,7 +6,7 @@ import { ENV, POSTHOG_API_KEY, POSTHOG_HOST } from "../utilities/config";
export const initPostHog = () => { export const initPostHog = () => {
// @ts-ignore // @ts-ignore
console.log("Hi there 👋") console.log("Hi there 👋");
try { try {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
// @ts-ignore // @ts-ignore
@@ -19,7 +19,7 @@ export const initPostHog = () => {
return posthog; return posthog;
} catch (e) { } catch (e) {
console.log("posthog err", e) console.log("posthog err", e);
} }
return undefined; return undefined;

View File

@@ -3,9 +3,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const Error = ({ text }: { text: string }): JSX.Element => { const Error = ({ text }: { text: string }): JSX.Element => {
return ( return (
<div className="relative flex flex-row justify-center m-auto items-center w-fit rounded-full"> <div className="relative m-auto flex w-fit flex-row items-center justify-center rounded-full">
<FontAwesomeIcon icon={faExclamationTriangle} className="text-red mt-1.5 mb-2 mx-2" /> <FontAwesomeIcon icon={faExclamationTriangle} className="mx-2 mt-1.5 mb-2 text-red" />
{text && <p className="relative top-0 text-red mr-2 text-sm py-1">{text}</p>} {text && <p className="relative top-0 mr-2 py-1 text-sm text-red">{text}</p>}
</div> </div>
); );
}; };

View File

@@ -39,16 +39,16 @@ const InputField = ({
if (isStatic === true) { if (isStatic === true) {
return ( return (
<div className="flex flex-col my-2 md:my-4 justify-center w-full max-w-md"> <div className="my-2 flex w-full max-w-md flex-col justify-center md:my-4">
<p className="text-sm font-semibold text-gray-400 mb-0.5">{label}</p> <p className="mb-0.5 text-sm font-semibold text-gray-400">{label}</p>
{text && <p className="text-xs text-gray-400 mb-2">{text}</p>} {text && <p className="mb-2 text-xs text-gray-400">{text}</p>}
<input <input
onChange={(e) => onChangeHandler(e.target.value)} onChange={(e) => onChangeHandler(e.target.value)}
type={type} type={type}
placeholder={placeholder} placeholder={placeholder}
value={value} value={value}
required={isRequired} required={isRequired}
className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none" className="text-md min-w-16 w-full rounded-md border border-gray-600 bg-bunker-800 p-2 text-gray-400 outline-none"
name={name} name={name}
readOnly readOnly
autoComplete={autoComplete} autoComplete={autoComplete}
@@ -58,12 +58,12 @@ const InputField = ({
); );
} }
return ( return (
<div className="flex-col w-full"> <div className="w-full flex-col">
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5"> <div className="mb-0.5 flex flex-row items-center text-mineshaft-300">
<p className="text-sm font-semibold mr-1">{label}</p> <p className="mr-1 text-sm font-semibold">{label}</p>
</div> </div>
<div <div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${ className={`group relative flex w-full max-w-2xl flex-col justify-center border ${
error ? "border-red" : "border-mineshaft-500" error ? "border-red" : "border-mineshaft-500"
} rounded-md`} } rounded-md`}
> >
@@ -75,11 +75,11 @@ const InputField = ({
required={isRequired} required={isRequired}
className={`${ className={`${
blurred blurred
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400" ? "text-bunker-800 focus:text-gray-400 active:text-gray-400 group-hover:text-gray-400"
: "" : ""
} ${ } ${
error ? "focus:ring-red/50" : "focus:ring-primary/50" error ? "focus:ring-red/50" : "focus:ring-primary/50"
} relative peer bg-mineshaft-900 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`} } text-md min-w-16 peer relative w-full rounded-md bg-mineshaft-900 p-2 text-gray-400 outline-none duration-200 focus:ring-4`}
name={name} name={name}
spellCheck="false" spellCheck="false"
autoComplete={autoComplete} autoComplete={autoComplete}
@@ -91,7 +91,7 @@ const InputField = ({
onClick={() => { onClick={() => {
setPasswordVisible(!passwordVisible); setPasswordVisible(!passwordVisible);
}} }}
className="absolute self-end mr-3 text-gray-400 cursor-pointer" className="absolute mr-3 cursor-pointer self-end text-gray-400"
> >
{passwordVisible ? ( {passwordVisible ? (
<FontAwesomeIcon icon={faEyeSlash} /> <FontAwesomeIcon icon={faEyeSlash} />
@@ -101,7 +101,7 @@ const InputField = ({
</button> </button>
)} )}
{blurred && ( {blurred && (
<div className="peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden"> <div className="peer absolute flex h-10 w-fit max-w-xl items-center overflow-hidden text-clip rounded-md text-gray-400/50 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible">
<p className="ml-2" /> <p className="ml-2" />
{value {value
.split("") .split("")
@@ -109,7 +109,7 @@ const InputField = ({
.map(() => ( .map(() => (
<FontAwesomeIcon <FontAwesomeIcon
key={guidGenerator()} key={guidGenerator()}
className="text-xxs mx-0.5" className="mx-0.5 text-xxs"
icon={faCircle} icon={faCircle}
/> />
))} ))}
@@ -121,7 +121,7 @@ const InputField = ({
</div> </div>
)} */} )} */}
</div> </div>
{error && <p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">{errorText}</p>} {error && <p className="mx-0 mt-0.5 mb-2 max-w-xs text-xs text-red">{errorText}</p>}
</div> </div>
); );
}; };

View File

@@ -34,19 +34,19 @@ const ListBox = ({
<Listbox value={isSelected} onChange={onChange}> <Listbox value={isSelected} onChange={onChange}>
<div className="relative"> <div className="relative">
<Listbox.Button <Listbox.Button
className={`text-gray-400 relative ${ className={`relative text-gray-400 ${
isFull ? "w-full" : "w-52" isFull ? "w-full" : "w-52"
} cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm`} } focus-visible:ring-offset-orange-300 cursor-default rounded-md bg-white/[0.07] py-2.5 pl-3 pr-10 text-left shadow-md duration-200 hover:bg-white/[0.11] focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 sm:text-sm`}
> >
<div className="flex flex-row"> <div className="flex flex-row">
{text} {text}
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300"> <span className="ml-1 block cursor-pointer truncate font-semibold text-gray-300">
{" "} {" "}
{isSelected} {isSelected}
</span> </span>
</div> </div>
{data && ( {data && (
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <div className="pointer-events-none absolute inset-y-0 right-0 flex cursor-pointer items-center pr-2">
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5" /> <FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5" />
</div> </div>
)} )}
@@ -58,16 +58,16 @@ const ListBox = ({
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="border border-mineshaft-700 z-[70] p-2 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-bunker text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm no-scrollbar no-scrollbar::-webkit-scrollbar"> <Listbox.Options className="no-scrollbar::-webkit-scrollbar absolute z-[70] mt-1 max-h-60 w-full overflow-auto rounded-md border border-mineshaft-700 bg-bunker p-2 text-base shadow-lg ring-1 ring-black ring-opacity-5 no-scrollbar focus:outline-none sm:text-sm">
{data.map((person, personIdx) => ( {data.map((person, personIdx) => (
<Listbox.Option <Listbox.Option
key={`${person}.${personIdx + 1}`} key={`${person}.${personIdx + 1}`}
className={({ active, selected }) => className={({ active, selected }) =>
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${ `relative my-0.5 cursor-default select-none rounded-md py-2 pl-10 pr-4 ${
selected ? "bg-white/10 text-gray-400 font-bold" : "" selected ? "bg-white/10 font-bold text-gray-400" : ""
} ${ } ${
active && !selected active && !selected
? "bg-white/5 text-mineshaft-200 cursor-pointer" ? "cursor-pointer bg-white/5 text-mineshaft-200"
: "text-gray-400" : "text-gray-400"
} ` } `
} }
@@ -83,7 +83,7 @@ const ListBox = ({
{person} {person}
</span> </span>
{selected ? ( {selected ? (
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3"> <span className="absolute inset-y-0 left-0 flex items-center rounded-lg pl-3 text-primary">
<FontAwesomeIcon icon={faCheck} className="text-md ml-1" /> <FontAwesomeIcon icon={faCheck} className="text-md ml-1" />
</span> </span>
) : null} ) : null}
@@ -92,9 +92,9 @@ const ListBox = ({
</Listbox.Option> </Listbox.Option>
))} ))}
{buttonAction && ( {buttonAction && (
<button type="button" onClick={buttonAction} className="cursor-pointer w-full"> <button type="button" onClick={buttonAction} className="w-full cursor-pointer">
<div className="my-0.5 relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-lime-300 duration-200 hover:text-black hover:font-semibold mt-2"> <div className="relative my-0.5 mt-2 flex cursor-pointer select-none justify-start rounded-md py-2 pl-10 pr-4 text-gray-400 duration-200 hover:bg-lime-300 hover:font-semibold hover:text-black">
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4"> <span className="absolute inset-y-0 left-0 flex items-center rounded-lg pl-3 pr-4">
<FontAwesomeIcon icon={faPlus} className="text-lg" /> <FontAwesomeIcon icon={faPlus} className="text-lg" />
</span> </span>
Add Project Add Project

View File

@@ -43,7 +43,7 @@ const Button = ({
loading, loading,
icon, icon,
iconDisabled, iconDisabled,
type = "button", type = "button"
}: ButtonProps): JSX.Element => { }: ButtonProps): JSX.Element => {
// Check if the button show always be 'active' - then true; // Check if the button show always be 'active' - then true;
// or if it should switch between 'active' and 'disabled' - then give the status // or if it should switch between 'active' and 'disabled' - then give the status
@@ -53,9 +53,13 @@ const Button = ({
"group m-auto md:m-0 inline-block rounded-md duration-200", "group m-auto md:m-0 inline-block rounded-md duration-200",
// Setting background colors and hover modes // Setting background colors and hover modes
color === "mineshaft" && activityStatus && "bg-mineshaft-800 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60", color === "mineshaft" &&
activityStatus &&
"bg-mineshaft-800 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60",
color === "mineshaft" && !activityStatus && "bg-mineshaft", color === "mineshaft" && !activityStatus && "bg-mineshaft",
(color === "primary" || !color) && activityStatus && "bg-primary border border-primary-400 opacity-80 hover:opacity-100", (color === "primary" || !color) &&
activityStatus &&
"bg-primary border border-primary-400 opacity-80 hover:opacity-100",
(color === "primary" || !color) && !activityStatus && "bg-primary", (color === "primary" || !color) && !activityStatus && "bg-primary",
color === "red" && "bg-red-800 border border-red", color === "red" && "bg-red-800 border border-red",
@@ -78,7 +82,9 @@ const Button = ({
color !== "mineshaft" && color !== "red" && color !== "none" && "text-black", color !== "mineshaft" && color !== "red" && color !== "none" && "text-black",
color === "red" && "text-gray-200", color === "red" && "text-gray-200",
color === "none" && "text-gray-200 text-xl", color === "none" && "text-gray-200 text-xl",
activityStatus && color !== "red" && color !== "mineshaft" && color !== "none" ? "group-hover:text-black" : "", activityStatus && color !== "red" && color !== "mineshaft" && color !== "none"
? "group-hover:text-black"
: "",
size === "icon" && "flex items-center justify-center" size === "icon" && "flex items-center justify-center"
); );
@@ -103,7 +109,7 @@ const Button = ({
<div <div
className={`${ className={`${
loading === true ? "opacity-100" : "opacity-0" loading === true ? "opacity-100" : "opacity-0"
} absolute flex items-center px-3 bg-primary duration-200 w-full`} } absolute flex w-full items-center bg-primary px-3 duration-200`}
> >
<Image <Image
src="/images/loading/loadingblack.gif" src="/images/loading/loadingblack.gif"
@@ -116,7 +122,7 @@ const Button = ({
{icon && ( {icon && (
<FontAwesomeIcon <FontAwesomeIcon
icon={icon} icon={icon}
className={`flex my-auto font-extrabold ${size === "icon-sm" ? "text-sm" : "text-sm"} ${ className={`my-auto flex font-extrabold ${size === "icon-sm" ? "text-sm" : "text-sm"} ${
(text || textDisabled) && "mr-2" (text || textDisabled) && "mr-2"
}`} }`}
/> />
@@ -124,7 +130,7 @@ const Button = ({
{iconDisabled && ( {iconDisabled && (
<FontAwesomeIcon <FontAwesomeIcon
icon={iconDisabled as IconProp} icon={iconDisabled as IconProp}
className={`flex my-auto font-extrabold ${size === "icon-sm" ? "text-sm" : "text-md"} ${ className={`my-auto flex font-extrabold ${size === "icon-sm" ? "text-sm" : "text-md"} ${
(text || textDisabled) && "mr-2" (text || textDisabled) && "mr-2"
}`} }`}
/> />

View File

@@ -64,7 +64,7 @@ const AddProjectMemberDialog = ({
) : ( ) : (
<Dialog.Title <Dialog.Title
as="h3" as="h3"
className="z-50 text-lg font-medium text-mineshaft-300 mb-4" className="z-50 mb-4 text-lg font-medium text-mineshaft-300"
> >
{t("section.members.add-dialog.already-all-invited")} {t("section.members.add-dialog.already-all-invited")}
</Dialog.Title> </Dialog.Title>
@@ -127,7 +127,9 @@ const AddProjectMemberDialog = ({
</div> </div>
) : ( ) : (
<Button <Button
onButtonPressed={() => router.push(`/org/${localStorage.getItem("orgData.id")}/members`)} onButtonPressed={() =>
router.push(`/org/${localStorage.getItem("orgData.id")}/members`)
}
color="mineshaft" color="mineshaft"
text={t("section.members.add-dialog.add-user-to-org") as string} text={t("section.members.add-dialog.add-user-to-org") as string}
size="md" size="md"

View File

@@ -28,11 +28,11 @@ export const AddUpdateEnvironmentDialog = ({
onCreateSubmit, onCreateSubmit,
onEditSubmit, onEditSubmit,
initialValues, initialValues,
isEditMode, isEditMode
}: Props) => { }: Props) => {
const [formInput, setFormInput] = useState<FormFields>({ const [formInput, setFormInput] = useState<FormFields>({
name: "", name: "",
slug: "", slug: ""
}); });
// This use effect can be removed when the unmount is happening from outside the component // This use effect can be removed when the unmount is happening from outside the component
@@ -50,7 +50,7 @@ export const AddUpdateEnvironmentDialog = ({
e.preventDefault(); e.preventDefault();
const data = { const data = {
name: formInput.name, name: formInput.name,
slug: formInput.slug.toLowerCase(), slug: formInput.slug.toLowerCase()
}; };
if (isEditMode) { if (isEditMode) {
onEditSubmit(data); onEditSubmit(data);
@@ -62,75 +62,70 @@ export const AddUpdateEnvironmentDialog = ({
return ( return (
<div> <div>
<Transition appear show={isOpen} as={Fragment}> <Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative z-20' onClose={onClose}> <Dialog as="div" className="relative z-20" onClose={onClose}>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter='ease-out duration-300' enter="ease-out duration-300"
enterFrom='opacity-0' enterFrom="opacity-0"
enterTo='opacity-100' enterTo="opacity-100"
leave='ease-out duration-150' leave="ease-out duration-150"
leaveFrom='opacity-100' leaveFrom="opacity-100"
leaveTo='opacity-0' leaveTo="opacity-0"
> >
<div className='fixed inset-0 bg-black bg-opacity-70' /> <div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child> </Transition.Child>
<div className='fixed inset-0 overflow-y-auto z-50'> <div className="fixed inset-0 z-50 overflow-y-auto">
<div className='flex min-h-full items-center justify-center p-4 text-center'> <div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter='ease-out duration-300' enter="ease-out duration-300"
enterFrom='opacity-0 scale-95' enterFrom="opacity-0 scale-95"
enterTo='opacity-100 scale-100' enterTo="opacity-100 scale-100"
leave='ease-in duration-200' leave="ease-in duration-200"
leaveFrom='opacity-100 scale-100' leaveFrom="opacity-100 scale-100"
leaveTo='opacity-0 scale-95' leaveTo="opacity-0 scale-95"
> >
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'> <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title <Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
as='h3' {isEditMode ? "Update environment" : "Create a new environment"}
className='text-lg font-medium leading-6 text-gray-400'
>
{isEditMode
? "Update environment"
: "Create a new environment"}
</Dialog.Title> </Dialog.Title>
<form onSubmit={onFormSubmit}> <form onSubmit={onFormSubmit}>
<div className='max-h-28 mt-4'> <div className="mt-4 max-h-28">
<InputField <InputField
label='Environment Name' label="Environment Name"
onChangeHandler={(val) => onInputChange("name", val)} onChangeHandler={(val) => onInputChange("name", val)}
type='varName' type="varName"
value={formInput.name} value={formInput.name}
placeholder='' placeholder=""
isRequired isRequired
// error={error.length > 0} // error={error.length > 0}
// errorText={error} // errorText={error}
/> />
</div> </div>
<div className='max-h-28 mt-4'> <div className="mt-4 max-h-28">
<InputField <InputField
label='Environment Slug' label="Environment Slug"
onChangeHandler={(val) => onInputChange("slug", val)} onChangeHandler={(val) => onInputChange("slug", val)}
type='varName' type="varName"
value={formInput.slug} value={formInput.slug}
placeholder='' placeholder=""
isRequired isRequired
// error={error.length > 0} // error={error.length > 0}
// errorText={error} // errorText={error}
/> />
</div> </div>
<p className='text-xs text-gray-500 mt-2'> <p className="mt-2 text-xs text-gray-500">
Slugs are shorthands used in cli to access environment Slugs are shorthands used in cli to access environment
</p> </p>
<div className='mt-4 max-w-min'> <div className="mt-4 max-w-min">
<Button <Button
onButtonPressed={() => null} onButtonPressed={() => null}
type='submit' type="submit"
color='mineshaft' color="mineshaft"
text={isEditMode ? "Update" : "Create"} text={isEditMode ? "Update" : "Create"}
active={formInput.name !== "" && formInput.slug !== ""} active={formInput.name !== "" && formInput.slug !== ""}
size='md' size="md"
/> />
</div> </div>
</form> </form>

View File

@@ -13,76 +13,63 @@ type Props = {
orgName: string; orgName: string;
}; };
const AddUserDialog = ({ const AddUserDialog = ({ isOpen, closeModal, submitModal, email, setEmail, orgName }: Props) => {
isOpen,
closeModal,
submitModal,
email,
setEmail,
orgName,
}: Props) => {
const submit = () => { const submit = () => {
submitModal(email); submitModal(email);
}; };
return ( return (
<div className='z-50'> <div className="z-50">
<Transition appear show={isOpen} as={Fragment}> <Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative' onClose={closeModal}> <Dialog as="div" className="relative" onClose={closeModal}>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter='ease-out duration-300' enter="ease-out duration-300"
enterFrom='opacity-0' enterFrom="opacity-0"
enterTo='opacity-100' enterTo="opacity-100"
leave='ease-in duration-200' leave="ease-in duration-200"
leaveFrom='opacity-100' leaveFrom="opacity-100"
leaveTo='opacity-0' leaveTo="opacity-0"
> >
<div className='fixed inset-0 bg-black bg-opacity-70' /> <div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child> </Transition.Child>
<div className='fixed inset-0 overflow-y-auto'> <div className="fixed inset-0 overflow-y-auto">
<div className='flex min-h-full items-center justify-center p-4 text-center'> <div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter='ease-out duration-300' enter="ease-out duration-300"
enterFrom='opacity-0 scale-95' enterFrom="opacity-0 scale-95"
enterTo='opacity-100 scale-100' enterTo="opacity-100 scale-100"
leave='ease-in duration-200' leave="ease-in duration-200"
leaveFrom='opacity-100 scale-100' leaveFrom="opacity-100 scale-100"
leaveTo='opacity-0 scale-95' leaveTo="opacity-0 scale-95"
> >
<Dialog.Panel className='w-full max-w-lg transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'> <Dialog.Panel className="w-full max-w-lg transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title <Dialog.Title
as='h3' as="h3"
className='text-lg font-medium leading-6 text-gray-400 z-50' className="z-50 text-lg font-medium leading-6 text-gray-400"
> >
Invite others to {orgName} Invite others to {orgName}
</Dialog.Title> </Dialog.Title>
<div className='mt-2 mb-4'> <div className="mt-2 mb-4">
<p className='text-sm text-gray-500'> <p className="text-sm text-gray-500">
An invite is specific to an email address and expires An invite is specific to an email address and expires after 1 day. For
after 1 day. For security reasons, you will need to security reasons, you will need to separately add members to projects.
separately add members to projects.
</p> </p>
</div> </div>
<div className='max-h-28'> <div className="max-h-28">
<InputField <InputField
label='Email' label="Email"
onChangeHandler={setEmail} onChangeHandler={setEmail}
type='varName' type="varName"
value={email} value={email}
placeholder='' placeholder=""
isRequired isRequired
/> />
</div> </div>
<div className='mt-4 max-w-max'> <div className="mt-4 max-w-max">
<Button <Button onButtonPressed={submit} color="mineshaft" text="Invite" size="md" />
onButtonPressed={submit}
color='mineshaft'
text='Invite'
size='md'
/>
</div> </div>
</Dialog.Panel> </Dialog.Panel>
{/* <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all"> {/* <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">

View File

@@ -5,7 +5,6 @@ import Button from "../buttons/Button";
import InputField from "../InputField"; import InputField from "../InputField";
import { Checkbox } from "../table/Checkbox"; import { Checkbox } from "../table/Checkbox";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
closeModal: () => void; closeModal: () => void;
@@ -26,8 +25,8 @@ const AddWorkspaceDialog = ({
workspaceName, workspaceName,
setWorkspaceName, setWorkspaceName,
error, error,
loading, loading
}:Props) => { }: Props) => {
const [addAllUsers, setAddAllUsers] = useState(true); const [addAllUsers, setAddAllUsers] = useState(true);
const submit = () => { const submit = () => {
submitModal(workspaceName, addAllUsers); submitModal(workspaceName, addAllUsers);
@@ -60,11 +59,8 @@ const AddWorkspaceDialog = ({
leaveFrom="opacity-100 scale-100" leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95" leaveTo="opacity-0 scale-95"
> >
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all"> <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title <Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
Create a new project Create a new project
</Dialog.Title> </Dialog.Title>
<div className="mt-2"> <div className="mt-2">
@@ -72,7 +68,7 @@ const AddWorkspaceDialog = ({
This project will contain your secrets and configs. This project will contain your secrets and configs.
</p> </p>
</div> </div>
<div className="max-h-28 mt-4"> <div className="mt-4 max-h-28">
<InputField <InputField
label="Project Name" label="Project Name"
onChangeHandler={setWorkspaceName} onChangeHandler={setWorkspaceName}
@@ -84,10 +80,7 @@ const AddWorkspaceDialog = ({
/> />
</div> </div>
<div className="mt-4 ml-1"> <div className="mt-4 ml-1">
<Checkbox <Checkbox addAllUsers={addAllUsers} setAddAllUsers={setAddAllUsers} />
addAllUsers={addAllUsers}
setAddAllUsers={setAddAllUsers}
/>
</div> </div>
<div className="mt-4 max-w-min"> <div className="mt-4 max-w-min">
<Button <Button

View File

@@ -6,20 +6,14 @@ import InputField from "../InputField";
// REFACTOR: Move all these modals into one reusable one // REFACTOR: Move all these modals into one reusable one
type Props = { type Props = {
isOpen?: boolean; isOpen?: boolean;
onClose: ()=>void; onClose: () => void;
title: string; title: string;
onSubmit:()=>void; onSubmit: () => void;
deleteKey?:string; deleteKey?: string;
} };
const DeleteActionModal = ({ const DeleteActionModal = ({ isOpen, onClose, title, onSubmit, deleteKey }: Props) => {
isOpen, const [deleteInputField, setDeleteInputField] = useState("");
onClose,
title,
onSubmit,
deleteKey
}:Props) => {
const [deleteInputField, setDeleteInputField] = useState("")
useEffect(() => { useEffect(() => {
setDeleteInputField(""); setDeleteInputField("");
@@ -28,64 +22,57 @@ const DeleteActionModal = ({
return ( return (
<div> <div>
<Transition appear show={isOpen} as={Fragment}> <Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative z-10' onClose={onClose}> <Dialog as="div" className="relative z-10" onClose={onClose}>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter='ease-out duration-300' enter="ease-out duration-300"
enterFrom='opacity-0' enterFrom="opacity-0"
enterTo='opacity-100' enterTo="opacity-100"
leave='ease-in duration-150' leave="ease-in duration-150"
leaveFrom='opacity-100' leaveFrom="opacity-100"
leaveTo='opacity-0' leaveTo="opacity-0"
> >
<div className='fixed inset-0 bg-black bg-opacity-70' /> <div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child> </Transition.Child>
<div className='fixed inset-0 overflow-y-auto'> <div className="fixed inset-0 overflow-y-auto">
<div className='flex min-h-full items-center justify-center p-4 text-center'> <div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter='ease-out duration-300' enter="ease-out duration-300"
enterFrom='opacity-0 scale-95' enterFrom="opacity-0 scale-95"
enterTo='opacity-100 scale-100' enterTo="opacity-100 scale-100"
leave='ease-in duration-200' leave="ease-in duration-200"
leaveFrom='opacity-100 scale-100' leaveFrom="opacity-100 scale-100"
leaveTo='opacity-0 scale-95' leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-md bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'>
<Dialog.Title
as='h3'
className='text-lg font-medium leading-6 text-gray-400'
> >
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-grey p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
{title} {title}
</Dialog.Title> </Dialog.Title>
<div className='mt-2'> <div className="mt-2">
<p className='text-sm text-gray-500'> <p className="text-sm text-gray-500">This action is irrevertible.</p>
This action is irrevertible.
</p>
</div> </div>
<div className='mt-2'> <div className="mt-2">
<InputField <InputField
isRequired isRequired
label={`Type ${deleteKey} to delete the resource`} label={`Type ${deleteKey} to delete the resource`}
onChangeHandler={(val) => setDeleteInputField(val)} onChangeHandler={(val) => setDeleteInputField(val)}
value={deleteInputField} value={deleteInputField}
type='text' type="text"
/> />
</div> </div>
<div className='mt-6'> <div className="mt-6">
<button <button
type='button' type="button"
className='inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2' className="hover:bg-alizarin hover:text-semibold inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onSubmit} onClick={onSubmit}
disabled={ disabled={Boolean(deleteKey) && deleteInputField !== deleteKey}
Boolean(deleteKey) && deleteInputField !== deleteKey
}
> >
Delete Delete
</button> </button>
<button <button
type='button' type="button"
className='ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2' className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:border-white hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onClose} onClick={onClose}
> >
Cancel Cancel

View File

@@ -5,13 +5,13 @@ import { Dialog, Transition } from "@headlessui/react";
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state. // #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
type Props = { type Props = {
isOpen: boolean isOpen: boolean;
onClose: () => void onClose: () => void;
onSubmit: () => void onSubmit: () => void;
} };
export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => { export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
const { t } = useTranslation() const { t } = useTranslation();
return ( return (
<div> <div>
<Transition appear show={isOpen} as={Fragment}> <Transition appear show={isOpen} as={Fragment}>
@@ -45,7 +45,7 @@ export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
leaveFrom="opacity-100 scale-100" leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95" leaveTo="opacity-0 scale-95"
> >
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker border border-mineshaft-600 p-6 text-left align-middle shadow-xl transition-all"> <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-mineshaft-600 bg-bunker p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-bunker-200"> <Dialog.Title as="h3" className="text-lg font-medium leading-6 text-bunker-200">
{t("dashboard:sidebar.delete-key-dialog.title")} {t("dashboard:sidebar.delete-key-dialog.title")}
</Dialog.Title> </Dialog.Title>
@@ -57,14 +57,14 @@ export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
<div className="mt-6 flex justify-start"> <div className="mt-6 flex justify-start">
<button <button
type="button" type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-red-500 opacity-80 hover:opacity-100 px-4 py-2 text-sm font-medium text-bunker-100 text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2" className="text-semibold inline-flex justify-center rounded-md border border-transparent bg-red-500 px-4 py-2 text-sm font-medium text-bunker-100 opacity-80 duration-200 hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onSubmit} onClick={onSubmit}
> >
Delete Delete
</button> </button>
<button <button
type="button" type="button"
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-bunker-500 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-mineshaft-500 hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2" className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-bunker-500 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:bg-mineshaft-500 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onClose} onClick={onClose}
> >
Cancel Cancel

View File

@@ -10,66 +10,55 @@ type Props = {
userIdToBeDeleted: string; userIdToBeDeleted: string;
}; };
const DeleteUserDialog = ({ const DeleteUserDialog = ({ isOpen, closeModal, submitModal, userIdToBeDeleted }: Props) => {
isOpen,
closeModal,
submitModal,
userIdToBeDeleted,
}: Props) => {
const submit = () => { const submit = () => {
submitModal(userIdToBeDeleted); submitModal(userIdToBeDeleted);
}; };
return ( return (
<div> <div>
<Transition appear show={isOpen} as={Fragment}> <Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative z-10' onClose={closeModal}> <Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter='ease-out duration-300' enter="ease-out duration-300"
enterFrom='opacity-0' enterFrom="opacity-0"
enterTo='opacity-100' enterTo="opacity-100"
leave='ease-in duration-200' leave="ease-in duration-200"
leaveFrom='opacity-100' leaveFrom="opacity-100"
leaveTo='opacity-0' leaveTo="opacity-0"
> >
<div className='fixed inset-0 bg-black bg-opacity-25' /> <div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child> </Transition.Child>
<div className='fixed inset-0 overflow-y-auto'> <div className="fixed inset-0 overflow-y-auto">
<div className='flex min-h-full items-center justify-center p-4 text-center'> <div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter='ease-out duration-300' enter="ease-out duration-300"
enterFrom='opacity-0 scale-95' enterFrom="opacity-0 scale-95"
enterTo='opacity-100 scale-100' enterTo="opacity-100 scale-100"
leave='ease-in duration-200' leave="ease-in duration-200"
leaveFrom='opacity-100 scale-100' leaveFrom="opacity-100 scale-100"
leaveTo='opacity-0 scale-95' leaveTo="opacity-0 scale-95"
> >
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-2xl bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'> <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl border border-gray-700 bg-grey p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title <Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
as='h3' Are you sure you want to remove this user from the workspace?
className='text-lg font-medium leading-6 text-gray-400'
>
Are you sure you want to remove this user from the
workspace?
</Dialog.Title> </Dialog.Title>
<div className='mt-2'> <div className="mt-2">
<p className='text-sm text-gray-500'> <p className="text-sm text-gray-500">This action is irrevertible.</p>
This action is irrevertible.
</p>
</div> </div>
<div className='mt-6'> <div className="mt-6">
<button <button
type='button' type="button"
className='inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2' className="hover:bg-alizarin hover:text-semibold inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={submit} onClick={submit}
> >
Delete Delete
</button> </button>
<button <button
type='button' type="button"
className='ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2' className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:border-white hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={submit} onClick={submit}
> >
Cancel Cancel

View File

@@ -34,27 +34,27 @@ const BottonRightPopup = ({
}: PopupProps): JSX.Element => { }: PopupProps): JSX.Element => {
return ( return (
<div <div
className="z-[100] drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-md absolute bottom-0 right-0 mr-6 mb-6" className="absolute bottom-0 right-0 z-[100] mr-6 mb-6 flex max-w-xl flex-col items-start rounded-md border border-gray-600/50 bg-bunker pt-3 pb-4 text-gray-200 drop-shadow-xl"
role="alert" role="alert"
> >
<div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6"> <div className="flex w-full flex-row items-center justify-between border-b border-gray-600/70 px-6 pb-3">
<div className="font-bold text-xl mr-2 mt-0.5 flex flex-row"> <div className="mr-2 mt-0.5 flex flex-row text-xl font-bold">
<div>{titleText}</div> <div>{titleText}</div>
<div className="ml-2.5">{emoji}</div> <div className="ml-2.5">{emoji}</div>
</div> </div>
<button className="mt-1" onClick={() => setCheckDocsPopUpVisible(false)} type="button"> <button className="mt-1" onClick={() => setCheckDocsPopUpVisible(false)} type="button">
<FontAwesomeIcon <FontAwesomeIcon
icon={faXmark} icon={faXmark}
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer" className="cursor-pointer text-2xl text-gray-400 duration-200 hover:text-red"
/> />
</button> </button>
</div> </div>
<div className="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">{textLine1}</div> <div className="mt-4 mb-0.5 block px-6 text-gray-300 sm:inline">{textLine1}</div>
<div className="block sm:inline mb-4 px-6">{textLine2}</div> <div className="mb-4 block px-6 sm:inline">{textLine2}</div>
<div className="flex flex-row px-6 w-full"> <div className="flex w-full flex-row px-6">
{/* eslint-disable-next-line react/jsx-no-target-blank */} {/* eslint-disable-next-line react/jsx-no-target-blank */}
<a <a
className="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center" className="flex w-full justify-center rounded-md bg-white/10 p-2 font-bold duration-200 hover:bg-primary hover:text-black"
href={buttonLink} href={buttonLink}
target="_blank" target="_blank"
rel="noopener" rel="noopener"

View File

@@ -9,7 +9,7 @@ export const Checkbox = ({ addAllUsers, setAddAllUsers }: Props) => (
{addAllUsers === true ? ( {addAllUsers === true ? (
<input <input
type="checkbox" type="checkbox"
className="accent-primary h-4 w-4" className="h-4 w-4 accent-primary"
checked checked
readOnly readOnly
onClick={() => setAddAllUsers(!addAllUsers)} onClick={() => setAddAllUsers(!addAllUsers)}
@@ -20,12 +20,12 @@ export const Checkbox = ({ addAllUsers, setAddAllUsers }: Props) => (
role="button" role="button"
tabIndex={0} tabIndex={0}
aria-label="add all users" aria-label="add all users"
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm" className="h-4 w-4 rounded-sm border border-gray-600 bg-bunker"
onClick={() => setAddAllUsers(!addAllUsers)} onClick={() => setAddAllUsers(!addAllUsers)}
/> />
)} )}
<label className="ml-2 text-gray-500 text-sm"> <label className="ml-2 text-sm text-gray-500">
Add all members of my organization to this project. Add all members of my organization to this project.
</label> </label>
</div> </div>

View File

@@ -36,25 +36,28 @@ const Notification = ({ notification, clearNotification }: NotificationProps) =>
return ( return (
<div <div
className="relative w-full flex items-center justify-between px-6 py-4 rounded-md border border-bunker-500 pointer-events-auto bg-mineshaft-700 mb-3 right-3" className="pointer-events-auto relative right-3 mb-3 flex w-full items-center justify-between rounded-md border border-bunker-500 bg-mineshaft-700 px-6 py-4"
role="alert" role="alert"
> >
{notification.type === "error" && ( {notification.type === "error" && (
<div className="absolute w-full h-1 bg-red top-0 left-0 rounded-t-md" /> <div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-red" />
)} )}
{notification.type === "success" && ( {notification.type === "success" && (
<div className="absolute w-full h-1 bg-green top-0 left-0 rounded-t-md" /> <div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-green" />
)} )}
{notification.type === "info" && ( {notification.type === "info" && (
<div className="absolute w-full h-1 bg-yellow top-0 left-0 rounded-t-md" /> <div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-yellow" />
)} )}
<p className="text-bunker-200 text-md font-base mt-0.5">{notification.text}</p> <p className="text-md font-base mt-0.5 text-bunker-200">{notification.text}</p>
<button <button
type="button" type="button"
className="rounded-lg" className="rounded-lg"
onClick={() => clearNotification(notification.text)} onClick={() => clearNotification(notification.text)}
> >
<FontAwesomeIcon className="absolute right-2 top-3 text-bunker-300 pl-2 w-4 h-4 hover:text-white" icon={faXmark} /> <FontAwesomeIcon
className="absolute right-2 top-3 h-4 w-4 pl-2 text-bunker-300 hover:text-white"
icon={faXmark}
/>
</button> </button>
</div> </div>
); );

View File

@@ -11,7 +11,7 @@ const Notifications = ({ notifications, clearNotification }: NoticationsProps) =
} }
return ( return (
<div className="hidden fixed z-50 md:flex md:flex-col-reverse gap-y-2 w-96 h-full right-2 bottom-2 pointer-events-none"> <div className="pointer-events-none fixed right-2 bottom-2 z-[100] hidden h-full w-96 gap-y-2 md:flex md:flex-col-reverse">
{notifications.map((notif) => ( {notifications.map((notif) => (
<Notification key={notif.text} notification={notif} clearNotification={clearNotification} /> <Notification key={notif.text} notification={notif} clearNotification={clearNotification} />
))} ))}

View File

@@ -30,9 +30,9 @@ const ConfirmEnvOverwriteModal = ({
onClose={onClose} onClose={onClose}
> >
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<p className='text-gray-400'>Your file contains the following duplicate secrets:</p> <p className="text-gray-400">Your file contains the following duplicate secrets:</p>
<p className="text-sm text-gray-500">{duplicateKeys.join(", ")}</p> <p className="text-sm text-gray-500">{duplicateKeys.join(", ")}</p>
<p className='text-md text-gray-400'>Are you sure you want to overwrite these secrets?</p> <p className="text-md text-gray-400">Are you sure you want to overwrite these secrets?</p>
</div> </div>
</ModalContent> </ModalContent>
</Modal> </Modal>

View File

@@ -1,5 +1,10 @@
import { memo, SyntheticEvent, useRef } from "react"; import { memo, SyntheticEvent, useRef } from "react";
import { faCircle, faCodeBranch, faExclamationCircle, faEye } from "@fortawesome/free-solid-svg-icons"; import {
faCircle,
faCodeBranch,
faExclamationCircle,
faEye
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import guidGenerator from "../utilities/randomId"; import guidGenerator from "../utilities/randomId";
@@ -61,28 +66,30 @@ const DashboardInputField = ({
const error = startsWithNumber || isDuplicate; const error = startsWithNumber || isDuplicate;
return ( return (
<div className={`relative flex-col w-full h-10 ${
error && value !== "" ? "bg-red/[0.15]" : ""
} ${
isSideBarOpen && "bg-mineshaft-700 duration-200"
}`}>
<div <div
className={`group relative flex flex-col justify-center items-center h-full ${ className={`relative h-10 w-full flex-col ${error && value !== "" ? "bg-red/[0.15]" : ""} ${
isSideBarOpen && "bg-mineshaft-700 duration-200"
}`}
>
<div
className={`group relative flex h-full flex-col items-center justify-center ${
error ? "w-max" : "w-full" error ? "w-max" : "w-full"
}`} }`}
> >
<input <input
onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)} onChange={(e) =>
onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)
}
type={type} type={type}
value={value} value={value}
className={`z-10 peer font-mono ph-no-capture bg-transparent h-full caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none ${ className={`ph-no-capture min-w-16 peer z-10 h-full w-full bg-transparent px-2 font-mono text-sm caret-bunker-200 outline-none ${
error ? "text-red-600 focus:text-red-500" : "text-bunker-300 focus:text-bunker-100" error ? "text-red-600 focus:text-red-500" : "text-bunker-300 focus:text-bunker-100"
} duration-200`} } duration-200`}
spellCheck="false" spellCheck="false"
/> />
</div> </div>
{startsWithNumber && ( {startsWithNumber && (
<div className='absolute right-2 top-2 text-red z-50'> <div className="absolute right-2 top-2 z-50 text-red">
<HoverObject <HoverObject
text="Secret names should not start with a number" text="Secret names should not start with a number"
icon={faExclamationCircle} icon={faExclamationCircle}
@@ -91,7 +98,7 @@ const DashboardInputField = ({
</div> </div>
)} )}
{isDuplicate && value !== "" && !startsWithNumber && ( {isDuplicate && value !== "" && !startsWithNumber && (
<div className='absolute right-2 top-2 text-red z-50'> <div className="absolute right-2 top-2 z-50 text-red">
<HoverObject <HoverObject
text="Secret names should be unique" text="Secret names should be unique"
icon={faExclamationCircle} icon={faExclamationCircle}
@@ -99,10 +106,15 @@ const DashboardInputField = ({
/> />
</div> </div>
)} )}
{!error && <div className={`absolute right-0 top-0 text-red z-50 bg-mineshaft-800 group-hover:bg-mineshaft-700 ${ {!error && (
<div
className={`absolute right-0 top-0 z-50 bg-mineshaft-800 text-red group-hover:bg-mineshaft-700 ${
overrideEnabled ? "visible" : "invisible group-hover:visible" overrideEnabled ? "visible" : "invisible group-hover:visible"
} cursor-pointer duration-0 h-10 flex items-center px-2`}> } duration-0 flex h-10 cursor-pointer items-center px-2`}
<button type="button" onClick={() => { >
<button
type="button"
onClick={() => {
if (modifyValueOverride) { if (modifyValueOverride) {
if (overrideEnabled === false) { if (overrideEnabled === false) {
modifyValueOverride("", id); modifyValueOverride("", id);
@@ -110,14 +122,20 @@ const DashboardInputField = ({
modifyValueOverride(undefined, id); modifyValueOverride(undefined, id);
} }
} }
}}> }}
>
<HoverObject <HoverObject
text={overrideEnabled ? "This secret is overriden with your personal value" : "You can override this secret with a personal value"} text={
overrideEnabled
? "This secret is overriden with your personal value"
: "You can override this secret with a personal value"
}
icon={faCodeBranch} icon={faCodeBranch}
color={overrideEnabled ? "primary" : "bunker-400"} color={overrideEnabled ? "primary" : "bunker-400"}
/> />
</button> </button>
</div>} </div>
)}
</div> </div>
); );
} }
@@ -127,20 +145,29 @@ const DashboardInputField = ({
return ( return (
<PopoverObject text={value || ""} onChangeHandler={onChangeHandler} id={id}> <PopoverObject text={value || ""} onChangeHandler={onChangeHandler} id={id}>
<div title={value} className={`relative flex-col w-full h-10 overflow-hidden ${
isSideBarOpen && "bg-mineshaft-700 duration-200"
}`}>
<div <div
className={`group relative flex flex-col justify-center items-center h-full ${ title={value}
className={`relative h-10 w-full flex-col overflow-hidden ${
isSideBarOpen && "bg-mineshaft-700 duration-200"
}`}
>
<div
className={`group relative flex h-full flex-col items-center justify-center ${
error ? "w-max" : "w-full" error ? "w-max" : "w-full"
}`} }`}
> >
{value?.split("\n")[0] ? <span className='ph-no-capture truncate break-all bg-transparent leading-tight text-xs px-2 w-full min-w-16 outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'> {value?.split("\n")[0] ? (
<span className="ph-no-capture min-w-16 placeholder w-full truncate break-all bg-transparent px-2 text-xs leading-tight text-bunker-300 outline-none duration-200 placeholder:text-bunker-400 focus:text-bunker-100 placeholder:focus:text-transparent">
{value?.split("\n")[0]} {value?.split("\n")[0]}
</span> : <span className='text-bunker-400'>-</span> } </span>
{value?.split("\n")[1] && <span className='ph-no-capture truncate break-all bg-transparent leading-tight text-xs px-2 w-full min-w-16 outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'> ) : (
<span className="text-bunker-400">-</span>
)}
{value?.split("\n")[1] && (
<span className="ph-no-capture min-w-16 placeholder w-full truncate break-all bg-transparent px-2 text-xs leading-tight text-bunker-300 outline-none duration-200 placeholder:text-bunker-400 focus:text-bunker-100 placeholder:focus:text-transparent">
{value?.split("\n")[1]} {value?.split("\n")[1]}
</span>} </span>
)}
</div> </div>
</div> </div>
</PopoverObject> </PopoverObject>
@@ -148,10 +175,10 @@ const DashboardInputField = ({
} }
if (type === "value") { if (type === "value") {
return ( return (
<div className="flex-col w-full"> <div className="w-full flex-col">
<div className="group relative whitespace-pre flex flex-col justify-center w-full"> <div className="group relative flex w-full flex-col justify-center whitespace-pre">
{overrideEnabled === true && ( {overrideEnabled === true && (
<div className="bg-primary-500 rounded-sm absolute top-[0.1rem] right-[0.1rem] z-0 w-min text-xxs px-1 text-black opacity-80"> <div className="absolute top-[0.1rem] right-[0.1rem] z-0 w-min rounded-sm bg-primary-500 px-1 text-xxs text-black opacity-80">
Override enabled Override enabled
</div> </div>
)} )}
@@ -160,20 +187,20 @@ const DashboardInputField = ({
onChange={(e) => onChangeHandler(e.target.value, id)} onChange={(e) => onChangeHandler(e.target.value, id)}
onScroll={syncScroll} onScroll={syncScroll}
className={`${ className={`${
blurred blurred ? "text-transparent focus:text-transparent active:text-transparent" : ""
? "text-transparent focus:text-transparent active:text-transparent" } ph-no-capture min-w-16 no-scrollbar::-webkit-scrollbar peer z-10 w-full bg-transparent px-2 py-2 font-mono text-sm text-transparent caret-white outline-none duration-200 no-scrollbar`}
: ""
} z-10 peer font-mono ph-no-capture bg-transparent caret-white text-transparent text-sm px-2 py-2 w-full min-w-16 outline-none duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
spellCheck="false" spellCheck="false"
/> />
<div <div
ref={ref} ref={ref}
className={`${ className={`${
blurred && !overrideEnabled blurred && !overrideEnabled
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400 duration-200" ? "text-bunker-800 duration-200 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400"
: "" : ""
} ${overrideEnabled ? "text-primary-300" : "text-gray-400"} } ${overrideEnabled ? "text-primary-300" : "text-gray-400"}
absolute flex flex-row whitespace-pre font-mono z-0 ${blurred ? "invisible" : "visible"} peer-focus:visible mt-0.5 ph-no-capture overflow-x-scroll bg-transparent h-10 text-sm px-2 py-2 w-full min-w-16 outline-none duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`} absolute z-0 flex flex-row whitespace-pre font-mono ${
blurred ? "invisible" : "visible"
} ph-no-capture min-w-16 no-scrollbar::-webkit-scrollbar mt-0.5 h-10 w-full overflow-x-scroll bg-transparent px-2 py-2 text-sm outline-none duration-100 no-scrollbar peer-focus:visible`}
> >
{value?.split(REGEX).map((word) => { {value?.split(REGEX).map((word) => {
if (word.match(REGEX) !== null) { if (word.match(REGEX) !== null) {
@@ -203,20 +230,24 @@ const DashboardInputField = ({
})} })}
</div> </div>
{blurred && ( {blurred && (
<div className={`absolute flex flex-row justify-between items-center z-0 peer pr-2 ${ <div
className={`peer absolute z-0 flex flex-row items-center justify-between pr-2 ${
isSideBarOpen ? "bg-mineshaft-700 duration-200" : "bg-mineshaft-800" isSideBarOpen ? "bg-mineshaft-700 duration-200" : "bg-mineshaft-800"
} peer-active:hidden peer-focus:hidden group-hover:bg-white/[0.00] duration-100 h-10 w-full text-bunker-400 text-clip`}> } h-10 w-full text-clip text-bunker-400 duration-100 group-hover:bg-white/[0.00] peer-focus:hidden peer-active:hidden`}
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar"> >
<div className="no-scrollbar::-webkit-scrollbar flex flex-row items-center overflow-x-scroll px-2 no-scrollbar">
{value?.split("").map(() => ( {value?.split("").map(() => (
<FontAwesomeIcon <FontAwesomeIcon
key={guidGenerator()} key={guidGenerator()}
className="text-xxs mr-0.5" className="mr-0.5 text-xxs"
icon={faCircle} icon={faCircle}
/> />
))} ))}
{value?.split("").length === 0 && <span className='text-bunker-400/80'>EMPTY</span>} {value?.split("").length === 0 && <span className="text-bunker-400/80">EMPTY</span>}
</div>
<div className="invisible z-[100] cursor-default group-hover:visible">
<FontAwesomeIcon icon={faEye} />
</div> </div>
<div className='invisible group-hover:visible cursor-default z-[100]'><FontAwesomeIcon icon={faEye} /></div>
</div> </div>
)} )}
</div> </div>

View File

@@ -1,4 +1,4 @@
import React from "react" import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -8,32 +8,35 @@ import Button from "../basic/buttons/Button";
type Props = { type Props = {
onSubmit: () => void; onSubmit: () => void;
isPlain?: boolean; isPlain?: boolean;
} };
export const DeleteActionButton = ({ onSubmit, isPlain }: Props) => { export const DeleteActionButton = ({ onSubmit, isPlain }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className={`${ <div
className={`${
!isPlain !isPlain
? "bg-[#9B3535] opacity-70 hover:opacity-100 w-[4.5rem] h-[2.5rem] rounded-md duration-200 ml-2" ? "ml-2 h-[2.5rem] w-[4.5rem] rounded-md bg-[#9B3535] opacity-70 duration-200 hover:opacity-100"
: "cursor-pointer w-[1.5rem] h-[2.35rem] mr-2 flex items-center justfy-center"}`}> : "justfy-center mr-2 flex h-[2.35rem] w-[1.5rem] cursor-pointer items-center"
{isPlain }`}
? <div >
{isPlain ? (
<div
onKeyDown={() => null} onKeyDown={() => null}
role="button" role="button"
tabIndex={0} tabIndex={0}
onClick={onSubmit} onClick={onSubmit}
className="invisible group-hover:visible" className="invisible group-hover:visible"
> >
<FontAwesomeIcon className="text-bunker-300 hover:text-red pl-2 pr-6 text-lg mt-0.5" icon={faXmark} /> <FontAwesomeIcon
className="mt-0.5 pl-2 pr-6 text-lg text-bunker-300 hover:text-red"
icon={faXmark}
/>
</div> </div>
: <Button ) : (
text={String(t("Delete"))} <Button text={String(t("Delete"))} color="red" size="md" onButtonPressed={onSubmit} />
color="red" )}
size="md"
onButtonPressed={onSubmit}
/>}
</div> </div>
) );
} };

View File

@@ -18,7 +18,7 @@ const DownloadSecretMenu = ({ data, env }: { data: SecretDataProps[]; env: strin
<Menu as="div" className="relative inline-block text-left"> <Menu as="div" className="relative inline-block text-left">
<Menu.Button <Menu.Button
as="div" as="div"
className="inline-flex w-full justify-center text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75" className="inline-flex w-full justify-center rounded-md text-sm font-medium text-gray-200 duration-200 hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
> >
<Button color="mineshaft" size="icon-md" icon={faDownload} onButtonPressed={() => {}} /> <Button color="mineshaft" size="icon-md" icon={faDownload} onButtonPressed={() => {}} />
</Menu.Button> </Menu.Button>
@@ -31,7 +31,7 @@ const DownloadSecretMenu = ({ data, env }: { data: SecretDataProps[]; env: strin
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="absolute z-[90] drop-shadow-xl right-0 mt-0.5 w-[12rem] origin-top-right rounded-md bg-bunker border border-mineshaft-500 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none p-2 space-y-2"> <Menu.Items className="absolute right-0 z-[90] mt-0.5 w-[12rem] origin-top-right space-y-2 rounded-md border border-mineshaft-500 bg-bunker p-2 shadow-lg ring-1 ring-black ring-opacity-5 drop-shadow-xl focus:outline-none">
<Menu.Item> <Menu.Item>
<Button <Button
color="mineshaft" color="mineshaft"

View File

@@ -56,7 +56,7 @@ export default function NavHeader({
return ( return (
<div className="flex flex-row items-center pt-6"> <div className="flex flex-row items-center pt-6">
<div className="mr-2 flex h-5 w-5 items-center justify-center rounded-md bg-primary text-sm text-black min-w-[1.25rem]"> <div className="mr-2 flex h-5 w-5 min-w-[1.25rem] items-center justify-center rounded-md bg-primary text-sm text-black">
{currentOrg?.name?.charAt(0)} {currentOrg?.name?.charAt(0)}
</div> </div>
<Link passHref legacyBehavior href={`/org/${currentOrg?.id}/overview`}> <Link passHref legacyBehavior href={`/org/${currentOrg?.id}/overview`}>

View File

@@ -6,7 +6,6 @@ import { useOrganization, useWorkspace } from "@app/context";
import { Select, SelectItem, Tooltip } from "../v2"; import { Select, SelectItem, Tooltip } from "../v2";
/** /**
* This is the component at the top of almost every page. * This is the component at the top of almost every page.
* It shows how to navigate to a certain page. * It shows how to navigate to a certain page.
@@ -39,10 +38,12 @@ export default function NavHeaderSecrets({
}): JSX.Element { }): JSX.Element {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const router = useRouter() const router = useRouter();
return ( return (
<div className={`${!isSnapshot && "absolute"} ml-6 flex flex-row items-center pt-6 cursor-default`}> <div
className={`${!isSnapshot && "absolute"} ml-6 flex cursor-default flex-row items-center pt-6`}
>
<div className="mr-3 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100"> <div className="mr-3 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
{currentOrg?.name?.charAt(0)} {currentOrg?.name?.charAt(0)}
</div> </div>
@@ -60,20 +61,27 @@ export default function NavHeaderSecrets({
</> </>
)} )}
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" /> <FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
{pageName === "Secrets" {pageName === "Secrets" ? (
? <a className="text-md font-medium text-primary/80 hover:text-primary" href={`${router.asPath.split("?")[0]}`}>{pageName}</a> <a
: <div className="text-md text-gray-400">{pageName}</div>} className="text-md font-medium text-primary/80 hover:text-primary"
{currentEnv && href={`${router.asPath.split("?")[0]}`}
>
{pageName}
</a>
) : (
<div className="text-md text-gray-400">{pageName}</div>
)}
{currentEnv && (
<> <>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-sm text-gray-400" /> <FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-sm text-gray-400" />
<div className='pl-3 rounded-md hover:bg-bunker-100/10'> <div className="rounded-md pl-3 hover:bg-bunker-100/10">
<Tooltip content="Select environment"> <Tooltip content="Select environment">
<Select <Select
value={userAvailableEnvs?.filter(uae => uae.name === currentEnv)[0]?.slug} value={userAvailableEnvs?.filter((uae) => uae.name === currentEnv)[0]?.slug}
onValueChange={(value) => { onValueChange={(value) => {
if (value && onEnvChange) onEnvChange(value); if (value && onEnvChange) onEnvChange(value);
}} }}
className="text-md pl-0 font-medium text-primary/80 hover:text-primary bg-transparent" className="text-md bg-transparent pl-0 font-medium text-primary/80 hover:text-primary"
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl" dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl"
> >
{userAvailableEnvs?.map(({ name, slug }) => ( {userAvailableEnvs?.map(({ name, slug }) => (
@@ -84,7 +92,8 @@ export default function NavHeaderSecrets({
</Select> </Select>
</Tooltip> </Tooltip>
</div> </div>
</>} </>
)}
</div> </div>
); );
} }

View File

@@ -3,9 +3,7 @@ import React, { useState } from "react";
import ReactCodeInput from "react-code-input"; import ReactCodeInput from "react-code-input";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { useSendVerificationEmail } from "@app/hooks/api";
useSendVerificationEmail
} from "@app/hooks/api";
import Error from "../basic/Error"; import Error from "../basic/Error";
import { Button } from "../v2"; import { Button } from "../v2";
@@ -90,8 +88,8 @@ export default function CodeInputStep({
return ( return (
<div className="mx-auto h-full w-full pb-4 md:px-8"> <div className="mx-auto h-full w-full pb-4 md:px-8">
<p className="text-md flex justify-center text-bunker-200">{t("signup.step2-message")}</p> <p className="text-md flex justify-center text-bunker-200">{t("signup.step2-message")}</p>
<p className="text-md flex justify-center font-semibold my-1 text-bunker-200">{email} </p> <p className="text-md my-1 flex justify-center font-semibold text-bunker-200">{email} </p>
<div className="hidden md:block w-max min-w-[20rem] mx-auto"> <div className="mx-auto hidden w-max min-w-[20rem] md:block">
<ReactCodeInput <ReactCodeInput
name="" name=""
inputMode="tel" inputMode="tel"
@@ -102,7 +100,7 @@ export default function CodeInputStep({
className="mt-6 mb-2" className="mt-6 mb-2"
/> />
</div> </div>
<div className="block md:hidden w-max mt-4 mx-auto"> <div className="mx-auto mt-4 block w-max md:hidden">
<ReactCodeInput <ReactCodeInput
name="" name=""
inputMode="tel" inputMode="tel"
@@ -114,26 +112,29 @@ export default function CodeInputStep({
/> />
</div> </div>
{codeError && <Error text={t("signup.step2-code-error")} />} {codeError && <Error text={t("signup.step2-code-error")} />}
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left"> <div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
<div className="text-l py-1 text-lg w-full"> <div className="text-l w-full py-1 text-lg">
<Button <Button
type="submit" type="submit"
onClick={incrementStep} onClick={incrementStep}
size="sm" size="sm"
isFullWidth isFullWidth
className='h-14' className="h-14"
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
isLoading={isCodeInputCheckLoading} isLoading={isCodeInputCheckLoading}
> {String(t("signup.verify"))} </Button> >
{" "}
{String(t("signup.verify"))}{" "}
</Button>
</div> </div>
</div> </div>
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2"> <div className="mx-auto flex max-h-24 w-full max-w-md flex-col items-center justify-center pt-2">
<div className="flex flex-row items-baseline gap-1 text-sm"> <div className="flex flex-row items-baseline gap-1 text-sm">
<span className="text-bunker-400">{t("signup.step2-resend-alert")}</span> <span className="text-bunker-400">{t("signup.step2-resend-alert")}</span>
<div className="mt-2 text-bunker-400 text-md flex flex-row"> <div className="text-md mt-2 flex flex-row text-bunker-400">
<button disabled={isLoading} onClick={resendVerificationEmail} type="button"> <button disabled={isLoading} onClick={resendVerificationEmail} type="button">
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'> <span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
{isResendingVerificationEmail {isResendingVerificationEmail
? t("signup.step2-resend-progress") ? t("signup.step2-resend-progress")
: t("signup.step2-resend-submit")} : t("signup.step2-resend-submit")}
@@ -141,7 +142,7 @@ export default function CodeInputStep({
</button> </button>
</div> </div>
</div> </div>
<p className="text-sm text-bunker-400 pb-2">{t("signup.step2-spam-alert")}</p> <p className="pb-2 text-sm text-bunker-400">{t("signup.step2-spam-alert")}</p>
</div> </div>
</div> </div>
); );

View File

@@ -57,19 +57,22 @@ export default function DonwloadBackupPDFStep({
}; };
return ( return (
<div className="flex flex-col items-center w-full h-full md:px-6 mx-auto mb-36 md:mb-16"> <div className="mx-auto mb-36 flex h-full w-full flex-col items-center md:mb-16 md:px-6">
<p className="text-xl text-center font-medium flex flex-col justify-center items-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200"> <p className="flex flex-col items-center justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 mb-6 text-6xl text-bunker-200" /> <FontAwesomeIcon
icon={faWarning}
className="ml-2 mr-3 mb-6 pt-1 text-6xl text-bunker-200"
/>
{t("signup.step4-message")} {t("signup.step4-message")}
</p> </p>
<div className="flex flex-col pb-2 bg-mineshaft-800 border border-mineshaft-600 items-center justify-center text-center lg:w-1/6 w-full md:min-w-[24rem] mt-8 max-w-md text-bunker-300 text-md rounded-md"> <div className="text-md mt-8 flex w-full max-w-md flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 pb-2 text-center text-bunker-300 md:min-w-[24rem] lg:w-1/6">
<div className="w-full mt-4 md:mt-8 flex flex-row text-center items-center m-2 text-bunker-300 rounded-md lg:w-1/6 lg:w-1/6 w-full md:min-w-[23rem] px-3 mx-auto"> <div className="m-2 mx-auto mt-4 flex w-full w-full flex-row items-center rounded-md px-3 text-center text-bunker-300 md:mt-8 md:min-w-[23rem] lg:w-1/6 lg:w-1/6">
<span className="mb-2"> <span className="mb-2">
{t("signup.step4-description1")} {t("signup.step4-description3")} {t("signup.step4-description1")} {t("signup.step4-description3")}
</span> </span>
</div> </div>
<div className="flex flex-col items-center px-3 justify-center mt-0 md:mt-4 mb-2 md:mb-4 lg:w-1/6 w-full md:min-w-[20rem] mt-2 md:max-w-md mx-auto text-sm text-center md:text-left"> <div className="mx-auto mt-0 mb-2 mt-2 flex w-full flex-col items-center justify-center px-3 text-center text-sm md:mt-4 md:mb-4 md:min-w-[20rem] md:max-w-md md:text-left lg:w-1/6">
<div className="text-l py-1 text-lg w-full"> <div className="text-l w-full py-1 text-lg">
<Button <Button
onClick={handleBackupKeyGenerate} onClick={handleBackupKeyGenerate}
size="sm" size="sm"

View File

@@ -52,13 +52,13 @@ export default function EnterEmailStep({
try { try {
await mutateAsync({ email }); await mutateAsync({ email });
incrementStep(); incrementStep();
} catch(e) { } catch (e) {
if (axios.isAxiosError(e)) { if (axios.isAxiosError(e)) {
const { message = "Something went wrong" } = e.response?.data as { message: string}; const { message = "Something went wrong" } = e.response?.data as { message: string };
createNotification({ createNotification({
type: "error", type: "error",
text: message text: message
}) });
} }
} }
} }
@@ -66,11 +66,11 @@ export default function EnterEmailStep({
return ( return (
<div> <div>
<div className="w-full md:px-6 mx-auto"> <div className="mx-auto w-full md:px-6">
<p className="text-xl font-medium flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200"> <p className="flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-xl font-medium text-transparent">
{t("signup.step1-start")} {t("signup.step1-start")}
</p> </p>
<div className="flex flex-col items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] m-auto rounded-lg mt-8"> <div className="m-auto mt-8 flex w-1/4 min-w-[20rem] flex-col items-center justify-center rounded-lg lg:w-1/6">
<Input <Input
placeholder="Enter your email address..." placeholder="Enter your email address..."
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
@@ -79,28 +79,35 @@ export default function EnterEmailStep({
autoComplete="username" autoComplete="username"
className="h-12" className="h-12"
/> />
{emailError && <p className="text-red-600 text-xs text-left w-full ml-1.5 mt-1.5">Please enter a valid email.</p>} {emailError && (
<p className="ml-1.5 mt-1.5 w-full text-left text-xs text-red-600">
Please enter a valid email.
</p>
)}
</div> </div>
<div className="flex flex-col items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left"> <div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-1/6">
<div className="text-l py-1 text-lg w-full"> <div className="text-l w-full py-1 text-lg">
<Button <Button
type="submit" type="submit"
onClick={emailCheck} onClick={emailCheck}
size="sm" size="sm"
isFullWidth isFullWidth
className='h-14' className="h-14"
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
isLoading={isLoading} isLoading={isLoading}
isDisabled={isLoading} isDisabled={isLoading}
> {String(t("signup.step1-submit"))} </Button> >
{" "}
{String(t("signup.step1-submit"))}{" "}
</Button>
</div> </div>
</div> </div>
</div> </div>
<div className="mx-auto mb-48 mt-2 flex w-full max-w-md flex-col items-center justify-center pt-2 md:mb-16 md:pb-2"> <div className="mx-auto mb-48 mt-2 flex w-full max-w-md flex-col items-center justify-center pt-2 md:mb-16 md:pb-2">
<Link href="/login"> <Link href="/login">
<button type="button" className="w-max pb-3 duration-200 hover:opacity-90"> <button type="button" className="w-max pb-3 duration-200 hover:opacity-90">
<span className="text-sm text-mineshaft-400 hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer"> <span className="cursor-pointer text-sm text-mineshaft-400 duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
{t("signup.already-have-account")} {t("signup.already-have-account")}
</span> </span>
</button> </button>

View File

@@ -40,55 +40,61 @@ export default function TeamInviteStep(): JSX.Element {
}; };
return ( return (
<div className="w-max mx-auto min-w-lg h-full pb-4 px-8 mb-64 md:mb-32"> <div className="min-w-lg mx-auto mb-64 h-full w-max px-8 pb-4 md:mb-32">
<p className="text-2xl font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200"> <p className="flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-2xl font-semibold text-transparent">
{t("signup.step5-invite-team")} {t("signup.step5-invite-team")}
</p> </p>
<p className="text-center flex justify-center text-bunker-400 md:mx-8 mb-6 mt-4"> <p className="mb-6 mt-4 flex justify-center text-center text-bunker-400 md:mx-8">
{t("signup.step5-subtitle")} {t("signup.step5-subtitle")}
</p> </p>
<div className="bg-mineshaft-800 border border-mineshaft-500 w-max mx-auto pt-6 pb-4 px-8 rounded-xl drop-shadow-xl mb-6"> <div className="mx-auto mb-6 w-max rounded-xl border border-mineshaft-500 bg-mineshaft-800 px-8 pt-6 pb-4 drop-shadow-xl">
<div> <div>
<div className="text-bunker-300 font-medium pl-1 pb-1 text-sm"> <div className="pl-1 pb-1 text-sm font-medium text-bunker-300">
<span>Emails</span> <span>Emails</span>
</div> </div>
<textarea <textarea
className="bg-mineshaft-900/70 min-w-[30rem] h-20 w-full placeholder:text-bunker-400 py-1 px-2 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70" className="h-20 w-full min-w-[30rem] rounded-md border border-mineshaft-500 bg-mineshaft-900/70 py-1 px-2 text-sm text-bunker-300 outline-none ring-primary-800 ring-opacity-70 placeholder:text-bunker-400 focus:ring-2"
value={emails} value={emails}
onChange={(e) => setEmails(e.target.value)} onChange={(e) => setEmails(e.target.value)}
placeholder="email@example.com, email2@example.com..." placeholder="email@example.com, email2@example.com..."
/> />
</div> </div>
<div className="flex flex-row items-end justify-end mt-0 md:mt-4 md:mb-2 w-full md:min-w-[30rem] mt-2 md:max-w-md mx-auto text-sm"> <div className="mx-auto mt-0 mt-2 flex w-full flex-row items-end justify-end text-sm md:mt-4 md:mb-2 md:min-w-[30rem] md:max-w-md">
<Button <Button
onClick={() => { onClick={() => {
if (serverDetails?.emailConfigured) { if (serverDetails?.emailConfigured) {
inviteUsers({ emails }) inviteUsers({ emails });
} else { } else {
handlePopUpOpen("setUpEmail"); handlePopUpOpen("setUpEmail");
} }
}} }}
size="sm" size="sm"
// isFullWidth // isFullWidth
className='h-10' className="h-10"
colorSchema="primary" colorSchema="primary"
variant="solid" variant="solid"
> {t("signup.step5-send-invites") ?? ""} </Button> >
{" "}
{t("signup.step5-send-invites") ?? ""}{" "}
</Button>
</div> </div>
<EmailServiceSetupModal <EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen} isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)} onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)}
/> />
</div> </div>
<div className="flex flex-row max-w-max min-w-28 items-center justify-center md:p-2 min-w-[20rem] max-h-24 mx-auto text-lg px-4 mt-4 mb-2"> <div className="min-w-28 mx-auto mt-4 mb-2 flex max-h-24 min-w-[20rem] max-w-max flex-row items-center justify-center px-4 text-lg md:p-2">
<Button <Button
onClick={redirectToHome} onClick={redirectToHome}
size="sm" size="sm"
isFullWidth isFullWidth
className='h-12' className="h-12"
colorSchema="secondary" colorSchema="secondary"
variant="outline" variant="outline"
> {t("signup.step5-skip") ?? "Skip"} </Button> >
{" "}
{t("signup.step5-skip") ?? "Skip"}{" "}
</Button>
</div> </div>
</div> </div>
); );

View File

@@ -196,10 +196,8 @@ export default function UserInfoStep({
const userOrgs = await fetchOrganizations(); const userOrgs = await fetchOrganizations();
const orgSlug = userOrgs[0]?.slug;
const orgId = userOrgs[0]?.id; const orgId = userOrgs[0]?.id;
const project = await ProjectService.initProject({ const project = await ProjectService.initProject({
organizationSlug: orgSlug,
projectName: "Example Project" projectName: "Example Project"
}); });

View File

@@ -1,17 +1,11 @@
import { import { getAuthToken, setAuthToken, setMfaTempToken, setSignupTempToken } from "@app/reactQuery";
getAuthToken,
setAuthToken,
setMfaTempToken,
setSignupTempToken} from "@app/reactQuery";
export const PROVIDER_AUTH_TOKEN_KEY = "infisical__provider-auth-token"; export const PROVIDER_AUTH_TOKEN_KEY = "infisical__provider-auth-token";
// depreciated: go for apiRequest module in config/api // depreciated: go for apiRequest module in config/api
export default class SecurityClient { export default class SecurityClient {
static setProviderAuthToken(tokenStr: string) { static setProviderAuthToken(tokenStr: string) {
localStorage.setItem(PROVIDER_AUTH_TOKEN_KEY, tokenStr || "") localStorage.setItem(PROVIDER_AUTH_TOKEN_KEY, tokenStr || "");
} }
static getProviderAuthToken() { static getProviderAuthToken() {

View File

@@ -3,9 +3,7 @@ import crypto from "crypto";
import jsrp from "jsrp"; import jsrp from "jsrp";
import { import { changePassword, srp1 } from "@app/hooks/api/auth/queries";
changePassword,
srp1} from "@app/hooks/api/auth/queries";
import Aes256Gcm from "./cryptography/aes-256-gcm"; import Aes256Gcm from "./cryptography/aes-256-gcm";
import { deriveArgonKey } from "./cryptography/crypto"; import { deriveArgonKey } from "./cryptography/crypto";
@@ -18,12 +16,13 @@ type Params = {
email: string; email: string;
currentPassword: string; currentPassword: string;
newPassword: string; newPassword: string;
} };
const attemptChangePassword = ({ email, currentPassword, newPassword }: Params): Promise<void> => { const attemptChangePassword = ({ email, currentPassword, newPassword }: Params): Promise<void> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
clientOldPassword.init({ username: email, password: currentPassword }, async () => { clientOldPassword.init({ username: email, password: currentPassword }, async () => {
let serverPublicKey; let salt; let serverPublicKey;
let salt;
try { try {
const clientPublicKey = clientOldPassword.getPublicKey(); const clientPublicKey = clientOldPassword.getPublicKey();
@@ -95,7 +94,6 @@ const attemptChangePassword = ({ email, currentPassword, newPassword }: Params):
console.error(err2); console.error(err2);
reject(err2); reject(err2);
} }
}); });
}); });
} catch (err) { } catch (err) {
@@ -104,6 +102,6 @@ const attemptChangePassword = ({ email, currentPassword, newPassword }: Params):
} }
}); });
}); });
} };
export default attemptChangePassword; export default attemptChangePassword;

View File

@@ -1,7 +1,7 @@
/* eslint-disable prefer-destructuring */ /* eslint-disable prefer-destructuring */
import jsrp from "jsrp"; import jsrp from "jsrp";
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries"; import { login1, verifyMfaToken } from "@app/hooks/api/auth/queries";
import KeyService from "@app/services/KeyService"; import KeyService from "@app/services/KeyService";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage"; import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
@@ -12,10 +12,10 @@ const client = new jsrp.client();
interface IsMfaLoginSuccessful { interface IsMfaLoginSuccessful {
success: boolean; success: boolean;
loginResponse:{ loginResponse: {
privateKey: string; privateKey: string;
JTWToken: string; JTWToken: string;
} };
} }
/** /**
@@ -33,20 +33,22 @@ const attemptLoginMfa = async ({
}: { }: {
email: string; email: string;
password: string; password: string;
providerAuthToken?: string, providerAuthToken?: string;
mfaToken: string; mfaToken: string;
}): Promise<IsMfaLoginSuccessful> => { }): Promise<IsMfaLoginSuccessful> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
client.init({ client.init(
{
username: email, username: email,
password password
}, async () => { },
async () => {
try { try {
const clientPublicKey = client.getPublicKey(); const clientPublicKey = client.getPublicKey();
const { salt } = await login1({ const { salt } = await login1({
email, email,
clientPublicKey, clientPublicKey,
providerAuthToken, providerAuthToken
}); });
const { const {
@@ -91,7 +93,7 @@ const attemptLoginMfa = async ({
resolve({ resolve({
success: true, success: true,
loginResponse:{ loginResponse: {
privateKey, privateKey,
JTWToken: token JTWToken: token
} }
@@ -99,8 +101,9 @@ const attemptLoginMfa = async ({
} catch (err) { } catch (err) {
reject(err); reject(err);
} }
}
);
}); });
}); };
}
export default attemptLoginMfa; export default attemptLoginMfa;

View File

@@ -1,7 +1,7 @@
/* eslint-disable prefer-destructuring */ /* eslint-disable prefer-destructuring */
import jsrp from "jsrp"; import jsrp from "jsrp";
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries"; import { login1, verifyMfaToken } from "@app/hooks/api/auth/queries";
import KeyService from "@app/services/KeyService"; import KeyService from "@app/services/KeyService";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage"; import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
@@ -25,20 +25,22 @@ const attemptLoginMfa = async ({
}: { }: {
email: string; email: string;
password: string; password: string;
providerAuthToken?: string, providerAuthToken?: string;
mfaToken: string; mfaToken: string;
}): Promise<Boolean> => { }): Promise<Boolean> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
client.init({ client.init(
{
username: email, username: email,
password password
}, async () => { },
async () => {
try { try {
const clientPublicKey = client.getPublicKey(); const clientPublicKey = client.getPublicKey();
const { salt } = await login1({ const { salt } = await login1({
email, email,
clientPublicKey, clientPublicKey,
providerAuthToken, providerAuthToken
}); });
const { const {
@@ -85,8 +87,9 @@ const attemptLoginMfa = async ({
} catch (err) { } catch (err) {
reject(err); reject(err);
} }
}
);
}); });
}); };
}
export default attemptLoginMfa; export default attemptLoginMfa;

View File

@@ -1,5 +1,11 @@
import { checkIsPasswordBreached } from "./checkIsPasswordBreached"; import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
import { escapeCharRegex, letterCharRegex, lowEntropyRegexes,numAndSpecialCharRegex, repeatedCharRegex } from "./passwordRegexes"; import {
escapeCharRegex,
letterCharRegex,
lowEntropyRegexes,
numAndSpecialCharRegex,
repeatedCharRegex
} from "./passwordRegexes";
interface PasswordCheckProps { interface PasswordCheckProps {
password: string; password: string;
@@ -29,40 +35,38 @@ const passwordCheck = async ({
{ {
name: "tooShort", name: "tooShort",
validator: (pwd: string) => pwd.length >= 14, validator: (pwd: string) => pwd.length >= 14,
setError: setPasswordErrorTooShort, setError: setPasswordErrorTooShort
}, },
{ {
name: "tooLong", name: "tooLong",
validator: (pwd: string) => pwd.length < 101, validator: (pwd: string) => pwd.length < 101,
setError: setPasswordErrorTooLong, setError: setPasswordErrorTooLong
}, },
{ {
name: "noLetterChar", name: "noLetterChar",
validator: (pwd: string) => letterCharRegex.test(pwd), validator: (pwd: string) => letterCharRegex.test(pwd),
setError: setPasswordErrorNoLetterChar, setError: setPasswordErrorNoLetterChar
}, },
{ {
name: "noNumOrSpecialChar", name: "noNumOrSpecialChar",
validator: (pwd: string) => numAndSpecialCharRegex.test(pwd), validator: (pwd: string) => numAndSpecialCharRegex.test(pwd),
setError: setPasswordErrorNoNumOrSpecialChar, setError: setPasswordErrorNoNumOrSpecialChar
}, },
{ {
name: "repeatedChar", name: "repeatedChar",
validator: (pwd: string) => !repeatedCharRegex.test(pwd), validator: (pwd: string) => !repeatedCharRegex.test(pwd),
setError: setPasswordErrorRepeatedChar, setError: setPasswordErrorRepeatedChar
}, },
{ {
name: "escapeChar", name: "escapeChar",
validator: (pwd: string) => !escapeCharRegex.test(pwd), validator: (pwd: string) => !escapeCharRegex.test(pwd),
setError: setPasswordErrorEscapeChar, setError: setPasswordErrorEscapeChar
}, },
{ {
name: "lowEntropy", name: "lowEntropy",
validator: (pwd: string) => ( validator: (pwd: string) => !lowEntropyRegexes.some((regex) => regex.test(pwd)),
!lowEntropyRegexes.some(regex => regex.test(pwd)) setError: setPasswordErrorLowEntropy
), }
setError: setPasswordErrorLowEntropy,
},
]; ];
const isBreached = await checkIsPasswordBreached(password); const isBreached = await checkIsPasswordBreached(password);
@@ -81,7 +85,7 @@ const passwordCheck = async ({
} else { } else {
test.setError(false); test.setError(false);
} }
}) });
return errorCheck; return errorCheck;
}; };

View File

@@ -17,25 +17,25 @@ function bufferToHex(buffer: ArrayBuffer): string {
return hexParts.join(""); return hexParts.join("");
} }
// see API details here: https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange // see API details here: https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange
// in short, the pending password is hashed (SHA-1), the first 5 chars are sliced and compared against a ranged hash table // in short, the pending password is hashed (SHA-1), the first 5 chars are sliced and compared against a ranged hash table
// this hash table is formed from the 5 char hash prefix (ie. 00000-FFFFF) so 16^5 results // this hash table is formed from the 5 char hash prefix (ie. 00000-FFFFF) so 16^5 results
// returns a hash table of 800-1000 results // returns a hash table of 800-1000 results
// padding has been added to prevent MitM attacker determining which hash table was called by the response size // padding has been added to prevent MitM attacker determining which hash table was called by the response size
// the last 35 chars of the password hash are compared client-side against the table // the last 35 chars of the password hash are compared client-side against the table
// if there is a match, that password has been involved in a password breach (ie. pwnd) and should NOT be accepted // if there is a match, that password has been involved in a password breach (ie. pwnd) and should NOT be accepted
// the database consists of ~700 mln breached passwords and is continuously updated, including with law enforcement ingestion // the database consists of ~700 mln breached passwords and is continuously updated, including with law enforcement ingestion
// https://www.troyhunt.com/open-source-pwned-passwords-with-fbi-feed-and-225m-new-nca-passwords-is-now-live/ // https://www.troyhunt.com/open-source-pwned-passwords-with-fbi-feed-and-225m-new-nca-passwords-is-now-live/
// The HIBP API follows NIST guidance (pg.14) https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf // The HIBP API follows NIST guidance (pg.14) https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf
// "When processing requests to establish and change memorized secrets, verifiers SHALL compare // "When processing requests to establish and change memorized secrets, verifiers SHALL compare
// the prospective secrets against a list that contains values known to be commonly-used, expected, // the prospective secrets against a list that contains values known to be commonly-used, expected,
// or compromised. For example, the list MAY include, but is not limited to: // or compromised. For example, the list MAY include, but is not limited to:
// • Passwords obtained from previous breach corpuses. // • Passwords obtained from previous breach corpuses.
// • Dictionary words. // • Dictionary words.
// • Repetitive or sequential characters (e.g. aaaaaa, 1234abcd). // • Repetitive or sequential characters (e.g. aaaaaa, 1234abcd).
// • Context-specific words, such as the name of the service, the username, and derivatives // • Context-specific words, such as the name of the service, the username, and derivatives
// thereof." // thereof."
export const checkIsPasswordBreached = async (password: string): Promise<boolean> => { export const checkIsPasswordBreached = async (password: string): Promise<boolean> => {
const HAVE_I_BEEN_PWNED_API_URL = "https://api.pwnedpasswords.com"; const HAVE_I_BEEN_PWNED_API_URL = "https://api.pwnedpasswords.com";
@@ -66,8 +66,8 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
response = await axios.get(rangedHashTableUri, { response = await axios.get(rangedHashTableUri, {
headers: { headers: {
"Add-Padding": "true", // see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/ "Add-Padding": "true", // see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/
"Content-Type": "text/plain", "Content-Type": "text/plain"
}, }
}); });
if (response.status === 200) { if (response.status === 200) {
@@ -78,7 +78,6 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
return isBreachedPassword; return isBreachedPassword;
} }
retryAttempt += 1; retryAttempt += 1;
} catch (err) { } catch (err) {
if (!axios.isAxiosError(err)) { if (!axios.isAxiosError(err)) {
throw err; throw err;
@@ -88,14 +87,15 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
} }
console.error( console.error(
`Received a non-200 response (${response ? response.status : "unknown"}) from the Pwnd Passwords API` `Received a non-200 response (${
response ? response.status : "unknown"
}) from the Pwnd Passwords API`
); );
return false; return false;
} catch (err: any) { } catch (err: any) {
console.error("An unexpected error has occurred:", err.message); console.error("An unexpected error has occurred:", err.message);
return false; return false;
} finally { } finally {
// Clear the UTF-8 encoded password from memory // Clear the UTF-8 encoded password from memory
if (encodedPwd) { if (encodedPwd) {

View File

@@ -1,5 +1,11 @@
import { checkIsPasswordBreached } from "./checkIsPasswordBreached"; import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
import { escapeCharRegex, letterCharRegex, lowEntropyRegexes,numAndSpecialCharRegex, repeatedCharRegex } from "./passwordRegexes"; import {
escapeCharRegex,
letterCharRegex,
lowEntropyRegexes,
numAndSpecialCharRegex,
repeatedCharRegex
} from "./passwordRegexes";
type Errors = { type Errors = {
tooShort?: string; tooShort?: string;
@@ -44,40 +50,38 @@ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Prom
{ {
name: "tooShort", name: "tooShort",
validator: (pwd: string) => pwd.length >= 14, validator: (pwd: string) => pwd.length >= 14,
errorText: "at least 14 characters", errorText: "at least 14 characters"
}, },
{ {
name: "tooLong", name: "tooLong",
validator: (pwd: string) => pwd.length < 101, validator: (pwd: string) => pwd.length < 101,
errorText: "at most 100 characters", errorText: "at most 100 characters"
}, },
{ {
name: "noLetterChar", name: "noLetterChar",
validator: (pwd: string) => letterCharRegex.test(pwd), validator: (pwd: string) => letterCharRegex.test(pwd),
errorText: "at least 1 letter character", errorText: "at least 1 letter character"
}, },
{ {
name: "noNumOrSpecialChar", name: "noNumOrSpecialChar",
validator: (pwd: string) => numAndSpecialCharRegex.test(pwd), validator: (pwd: string) => numAndSpecialCharRegex.test(pwd),
errorText: "at least 1 number or special character", errorText: "at least 1 number or special character"
}, },
{ {
name: "repeatedChar", name: "repeatedChar",
validator: (pwd: string) => !repeatedCharRegex.test(pwd), validator: (pwd: string) => !repeatedCharRegex.test(pwd),
errorText: "at most 3 repeated, consecutive characters", errorText: "at most 3 repeated, consecutive characters"
}, },
{ {
name: "escapeChar", name: "escapeChar",
validator: (pwd: string) => !escapeCharRegex.test(pwd), validator: (pwd: string) => !escapeCharRegex.test(pwd),
errorText: "No escape characters allowed.", errorText: "No escape characters allowed."
}, },
{ {
name: "lowEntropy", name: "lowEntropy",
validator: (pwd: string) => ( validator: (pwd: string) => !lowEntropyRegexes.some((regex) => regex.test(pwd)),
!lowEntropyRegexes.some(regex => regex.test(pwd)) errorText: "Password contains personal info."
), }
errorText: "Password contains personal info.",
},
]; ];
const isBreached = await checkIsPasswordBreached(password); const isBreached = await checkIsPasswordBreached(password);

View File

@@ -1,6 +1,7 @@
// This regex covers letters (case insensitive) for the top 50 most spoken languages // This regex covers letters (case insensitive) for the top 50 most spoken languages
/* eslint-disable no-misleading-character-class */ /* eslint-disable no-misleading-character-class */
export const letterCharRegex = /[A-Za-z\u00C0-\u00D6\u00D8-\u00DE\u00DF-\u00F6\u00F8-\u00FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00C7\u00FC\u00FB\u00EB\u00E7]/u; export const letterCharRegex =
/[A-Za-z\u00C0-\u00D6\u00D8-\u00DE\u00DF-\u00F6\u00F8-\u00FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00C7\u00FC\u00FB\u00EB\u00E7]/u;
// This regex covers digits, special characters, symbols, and emojis. // This regex covers digits, special characters, symbols, and emojis.
export const numAndSpecialCharRegex = /[\d!@#$%^&*(),.?":{}|<>]|[^\p{L}\p{N}\s]/u; export const numAndSpecialCharRegex = /[\d!@#$%^&*(),.?":{}|<>]|[^\p{L}\p{N}\s]/u;
@@ -32,5 +33,5 @@ export const lowEntropyRegexes = [
/\b(?:[A-Z0-9]{7,10}|[A-Z0-9]{10,11}|[A-Z0-9]{7,10})\b/, /\b(?:[A-Z0-9]{7,10}|[A-Z0-9]{10,11}|[A-Z0-9]{7,10})\b/,
// US social security number // US social security number
/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/, /\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/
]; ];

View File

@@ -3,9 +3,7 @@ import crypto from "crypto";
import jsrp from "jsrp"; import jsrp from "jsrp";
import { issueBackupPrivateKey , import { issueBackupPrivateKey, srp1 } from "@app/hooks/api/auth/queries";
srp1
} from "@app/hooks/api/auth/queries";
import generateBackupPDF from "../generateBackupPDF"; import generateBackupPDF from "../generateBackupPDF";
import Aes256Gcm from "./aes-256-gcm"; import Aes256Gcm from "./aes-256-gcm";
@@ -97,7 +95,6 @@ const issueBackupKey = async ({
generatedKey generatedKey
}); });
setBackupKeyIssued(true); setBackupKeyIssued(true);
} catch { } catch {
setBackupKeyError(true); setBackupKeyError(true);
} }

View File

@@ -7,6 +7,7 @@
/* eslint-disable vars-on-top */ /* eslint-disable vars-on-top */
/* eslint-disable no-var */ /* eslint-disable no-var */
/* eslint-disable func-names */ /* eslint-disable func-names */
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck // @ts-nocheck
import { INTERCOMid as APPid } from "@app/components/utilities/config"; import { INTERCOMid as APPid } from "@app/components/utilities/config";

View File

@@ -3,11 +3,7 @@ import { useRouter } from "next/router";
import { useUser } from "@app/context"; import { useUser } from "@app/context";
import { import { boot as bootIntercom, load as loadIntercom, update as updateIntercom } from "./intercom";
boot as bootIntercom,
load as loadIntercom,
update as updateIntercom,
} from "./intercom";
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
export const IntercomProvider = ({ children }: { children: any }) => { export const IntercomProvider = ({ children }: { children: any }) => {
@@ -16,7 +12,11 @@ export const IntercomProvider = ({ children }: { children: any }) => {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
loadIntercom(); loadIntercom();
bootIntercom({name: `${user?.firstName || ""} ${user?.lastName || ""}`, email: user?.email || "", created_at: Math.floor(((new Date(user?.createdAt))?.getTime() || 0) / 1000)}); bootIntercom({
name: `${user?.firstName || ""} ${user?.lastName || ""}`,
email: user?.email || "",
created_at: Math.floor((new Date(user?.createdAt)?.getTime() || 0) / 1000)
});
} }
useEffect(() => { useEffect(() => {

View File

@@ -2,4 +2,4 @@ export const isValidHexColor = (hexColor: string) => {
const hexColorPattern = /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/; const hexColorPattern = /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
return hexColorPattern.test(hexColor); return hexColorPattern.test(hexColor);
} };

View File

@@ -17,10 +17,9 @@ export const saveTokenToLocalStorage = ({
encryptedPrivateKey, encryptedPrivateKey,
iv, iv,
tag, tag,
privateKey, privateKey
}: Props) => { }: Props) => {
try { try {
if (protectedKey) { if (protectedKey) {
localStorage.removeItem("protectedKey"); localStorage.removeItem("protectedKey");
localStorage.setItem("protectedKey", protectedKey); localStorage.setItem("protectedKey", protectedKey);
@@ -62,9 +61,7 @@ export const saveTokenToLocalStorage = ({
} }
} catch (err) { } catch (err) {
if (err instanceof Error) { if (err instanceof Error) {
throw new Error( throw new Error(`Unable to send the tokens in local storage:${err.message}`);
`Unable to send the tokens in local storage:${ err.message}`
);
} }
} }
}; };

View File

@@ -1,7 +1,7 @@
/* eslint-disable */ /* eslint-disable */
import { PostHog } from 'posthog-js'; import { PostHog } from "posthog-js";
import { initPostHog } from '@app/components/analytics/posthog'; import { initPostHog } from "@app/components/analytics/posthog";
import { ENV } from '@app/components/utilities/config'; import { ENV } from "@app/components/utilities/config";
declare let TELEMETRY_CAPTURING_ENABLED: any; declare let TELEMETRY_CAPTURING_ENABLED: any;
@@ -13,23 +13,23 @@ class Capturer {
} }
capture(item: string) { capture(item: string) {
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED === "true") { if (ENV === "production" && TELEMETRY_CAPTURING_ENABLED === "true") {
try { try {
this.api.capture(item); this.api.capture(item);
} catch (error) { } catch (error) {
console.error('PostHog', error); console.error("PostHog", error);
} }
} }
} }
identify(id: string, email?: string) { identify(id: string, email?: string) {
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED === "true") { if (ENV === "production" && TELEMETRY_CAPTURING_ENABLED === "true") {
try { try {
this.api.identify(id, { this.api.identify(id, {
email: email email: email
}); });
} catch (error) { } catch (error) {
console.error('PostHog', error); console.error("PostHog", error);
} }
} }
} }

View File

@@ -14,7 +14,7 @@ type Story = StoryObj<typeof Accordion>;
export const Basic: Story = { export const Basic: Story = {
render: (args) => ( render: (args) => (
<div className="flex justify-center w-full"> <div className="flex w-full justify-center">
<Accordion {...args}> <Accordion {...args}>
<AccordionItem value="section-1"> <AccordionItem value="section-1">
<AccordionTrigger>Section 1</AccordionTrigger> <AccordionTrigger>Section 1</AccordionTrigger>

View File

@@ -8,7 +8,7 @@ export const AccordionItem = forwardRef<HTMLDivElement, AccordionPrimitive.Accor
({ children, className, ...props }, forwardedRef) => ( ({ children, className, ...props }, forwardedRef) => (
<AccordionPrimitive.Item <AccordionPrimitive.Item
className={twMerge( className={twMerge(
"mt-px overflow-hidden first:mt-0 data-[state=open]:border-l data-[state=open]:border-primary transition-all border-transparent", "mt-px overflow-hidden border-transparent transition-all first:mt-0 data-[state=open]:border-l data-[state=open]:border-primary",
className className
)} )}
{...props} {...props}
@@ -27,7 +27,7 @@ export const AccordionTrigger = forwardRef<
<AccordionPrimitive.Header className="flex"> <AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger <AccordionPrimitive.Trigger
className={twMerge( className={twMerge(
"py-2 px-4 group data-[state=open]:text-primary h-11 hover:text-primary flex flex-1 outline-none items-center justify-between ", "group flex h-11 flex-1 items-center justify-between py-2 px-4 outline-none hover:text-primary data-[state=open]:text-primary ",
className className
)} )}
{...props} {...props}
@@ -36,7 +36,7 @@ export const AccordionTrigger = forwardRef<
{children} {children}
<FontAwesomeIcon <FontAwesomeIcon
icon={faChevronDown} icon={faChevronDown}
className="ease-[cubic-bezier(0.87,_0,_0.13,_1)] transition-transform duration-300 group-data-[state=open]:rotate-180 text-sm" className="text-sm transition-transform duration-300 ease-[cubic-bezier(0.87,_0,_0.13,_1)] group-data-[state=open]:rotate-180"
aria-hidden aria-hidden
/> />
</AccordionPrimitive.Trigger> </AccordionPrimitive.Trigger>
@@ -51,13 +51,13 @@ export const AccordionContent = forwardRef<
>(({ children, className, ...props }, forwardedRef) => ( >(({ children, className, ...props }, forwardedRef) => (
<AccordionPrimitive.Content <AccordionPrimitive.Content
className={twMerge( className={twMerge(
"data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp overflow-hidden", "overflow-hidden data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp",
className className
)} )}
{...props} {...props}
ref={forwardedRef} ref={forwardedRef}
> >
<div className="text-sm py-2 px-4">{children}</div> <div className="py-2 px-4 text-sm">{children}</div>
</AccordionPrimitive.Content> </AccordionPrimitive.Content>
)); ));

View File

@@ -1 +1 @@
export { Accordion, AccordionContent, AccordionItem,AccordionTrigger } from "./Accordion"; export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./Accordion";

View File

@@ -10,7 +10,7 @@ export type CardTitleProps = {
export const CardTitle = ({ children, className, subTitle }: CardTitleProps) => ( export const CardTitle = ({ children, className, subTitle }: CardTitleProps) => (
<div <div
className={twMerge( className={twMerge(
"px-6 py-4 mb-5 font-sans text-lg font-normal border-b border-mineshaft-600 break-words", "mb-5 break-words border-b border-mineshaft-600 px-6 py-4 font-sans text-lg font-normal",
className className
)} )}
> >

View File

@@ -30,7 +30,7 @@ export const Checkbox = ({
<div className="flex items-center font-inter text-bunker-300"> <div className="flex items-center font-inter text-bunker-300">
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
className={twMerge( className={twMerge(
"flex items-center flex-shrink-0 justify-center w-4 h-4 transition-all rounded shadow border border-mineshaft-400 hover:bg-mineshaft-500 bg-mineshaft-600", "flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border border-mineshaft-400 bg-mineshaft-600 shadow transition-all hover:bg-mineshaft-500",
isDisabled && "bg-bunker-400 hover:bg-bunker-400", isDisabled && "bg-bunker-400 hover:bg-bunker-400",
isChecked && "bg-primary hover:bg-primary", isChecked && "bg-primary hover:bg-primary",
Boolean(children) && "mr-3", Boolean(children) && "mr-3",
@@ -46,7 +46,7 @@ export const Checkbox = ({
<FontAwesomeIcon icon={faCheck} size="sm" /> <FontAwesomeIcon icon={faCheck} size="sm" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
<label className="text-sm whitespace-nowrap truncate" htmlFor={id}> <label className="truncate whitespace-nowrap text-sm" htmlFor={id}>
{children} {children}
{isRequired && <span className="pl-1 text-red">*</span>} {isRequired && <span className="pl-1 text-red">*</span>}
</label> </label>

View File

@@ -58,7 +58,7 @@ export const DeleteActionModal = ({
title={title} title={title}
subTitle={subTitle} subTitle={subTitle}
footerContent={ footerContent={
<div className="flex items-center mx-2"> <div className="mx-2 flex items-center">
<Button <Button
className="mr-4" className="mr-4"
colorSchema="danger" colorSchema="danger"
@@ -91,7 +91,11 @@ export const DeleteActionModal = ({
} }
className="mb-0" className="mb-0"
> >
<Input value={inputData} onChange={(e) => setInputData(e.target.value)} placeholder="Type to delete..." /> <Input
value={inputData}
onChange={(e) => setInputData(e.target.value)}
placeholder="Type to delete..."
/>
</FormControl> </FormControl>
</form> </form>
</ModalContent> </ModalContent>

View File

@@ -45,7 +45,7 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
ref={forwardedRef} ref={forwardedRef}
className={twMerge(drawerContentVariation({ direction, className }))} className={twMerge(drawerContentVariation({ direction, className }))}
> >
<Card isRounded={false} className="h-full w-full dark"> <Card isRounded={false} className="dark h-full w-full">
{title && ( {title && (
<CardTitle subTitle={subTitle} className="px-4"> <CardTitle subTitle={subTitle} className="px-4">
{title} {title}

View File

@@ -1 +1 @@
export { Drawer, DrawerClose,DrawerContent, DrawerTrigger } from "./Drawer"; export { Drawer, DrawerClose, DrawerContent, DrawerTrigger } from "./Drawer";

View File

@@ -25,7 +25,7 @@ type Story = StoryObj<typeof DropdownMenuContent>;
export const Basic: Story = { export const Basic: Story = {
render: (args) => ( render: (args) => (
<div className="flex justify-center w-full"> <div className="flex w-full justify-center">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<IconButton ariaLabel="add"> <IconButton ariaLabel="add">
@@ -43,7 +43,7 @@ export const Basic: Story = {
export const Icons: Story = { export const Icons: Story = {
render: (args) => ( render: (args) => (
<div className="flex justify-center w-full"> <div className="flex w-full justify-center">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<IconButton ariaLabel="add"> <IconButton ariaLabel="add">
@@ -65,7 +65,7 @@ export const Icons: Story = {
export const WithDivider: Story = { export const WithDivider: Story = {
render: (args) => ( render: (args) => (
<div className="flex justify-center w-full"> <div className="flex w-full justify-center">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<IconButton ariaLabel="add"> <IconButton ariaLabel="add">
@@ -86,7 +86,7 @@ export const WithDivider: Story = {
export const Group: Story = { export const Group: Story = {
render: (args) => ( render: (args) => (
<div className="flex justify-center w-full"> <div className="flex w-full justify-center">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<IconButton ariaLabel="add"> <IconButton ariaLabel="add">

View File

@@ -24,7 +24,7 @@ export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuConten
{...props} {...props}
ref={forwardedRef} ref={forwardedRef}
className={twMerge( className={twMerge(
"min-w-[220px] z-30 bg-mineshaft-900 border border-mineshaft-600 will-change-auto text-bunker-300 rounded-md shadow data-[side=top]:animate-slideDownAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade", "z-30 min-w-[220px] rounded-md border border-mineshaft-600 bg-mineshaft-900 text-bunker-300 shadow will-change-auto data-[side=top]:animate-slideDownAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade",
className className
)} )}
> >
@@ -48,7 +48,7 @@ export const DropdownSubMenuContent = forwardRef<HTMLDivElement, DropdownSubMenu
{...props} {...props}
ref={forwardedRef} ref={forwardedRef}
className={twMerge( className={twMerge(
"min-w-[220px] z-30 bg-mineshaft-900 border border-mineshaft-600 will-change-auto text-bunker-300 rounded-md shadow data-[side=top]:animate-slideDownAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade", "z-30 min-w-[220px] rounded-md border border-mineshaft-600 bg-mineshaft-900 text-bunker-300 shadow will-change-auto data-[side=top]:animate-slideDownAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade",
className className
)} )}
> >
@@ -66,7 +66,7 @@ export type DropdownLabelProps = DropdownMenuPrimitive.MenuLabelProps;
export const DropdownMenuLabel = ({ className, ...props }: DropdownLabelProps) => ( export const DropdownMenuLabel = ({ className, ...props }: DropdownLabelProps) => (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
{...props} {...props}
className={twMerge("text-xs text-bunker-400 px-4 pt-2 pb-1", className)} className={twMerge("px-4 pt-2 pb-1 text-xs text-bunker-400", className)}
/> />
); );
@@ -91,14 +91,14 @@ export const DropdownMenuItem = <T extends ElementType = "button">({
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
{...props} {...props}
className={twMerge( className={twMerge(
"text-xs text-mineshaft-200 block font-inter px-4 py-2 data-[highlighted]:bg-mineshaft-700 rounded-sm outline-none cursor-pointer", "block cursor-pointer rounded-sm px-4 py-2 font-inter text-xs text-mineshaft-200 outline-none data-[highlighted]:bg-mineshaft-700",
className className
)} )}
> >
<Item type="button" role="menuitem" className="flex w-full items-center" ref={inputRef}> <Item type="button" role="menuitem" className="flex w-full items-center" ref={inputRef}>
{icon && iconPos === "left" && <span className="flex items-center mr-2">{icon}</span>} {icon && iconPos === "left" && <span className="mr-2 flex items-center">{icon}</span>}
<span className="flex-grow text-left">{children}</span> <span className="flex-grow text-left">{children}</span>
{icon && iconPos === "right" && <span className="flex items-center ml-2">{icon}</span>} {icon && iconPos === "right" && <span className="ml-2 flex items-center">{icon}</span>}
</Item> </Item>
</DropdownMenuPrimitive.Item> </DropdownMenuPrimitive.Item>
); );
@@ -124,14 +124,14 @@ export const DropdownSubMenuTrigger = <T extends ElementType = "button">({
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
{...props} {...props}
className={twMerge( className={twMerge(
"text-xs text-mineshaft-200 block font-inter px-4 py-2 data-[highlighted]:bg-mineshaft-700 rounded-sm outline-none cursor-pointer", "block cursor-pointer rounded-sm px-4 py-2 font-inter text-xs text-mineshaft-200 outline-none data-[highlighted]:bg-mineshaft-700",
className className
)} )}
> >
<Item type="button" role="menuitem" className="flex w-full items-center" ref={inputRef}> <Item type="button" role="menuitem" className="flex w-full items-center" ref={inputRef}>
{icon && iconPos === "left" && <span className="flex items-center mr-2">{icon}</span>} {icon && iconPos === "left" && <span className="mr-2 flex items-center">{icon}</span>}
<span className="flex-grow text-left">{children}</span> <span className="flex-grow text-left">{children}</span>
{icon && iconPos === "right" && <span className="flex items-center ml-2">{icon}</span>} {icon && iconPos === "right" && <span className="ml-2 flex items-center">{icon}</span>}
</Item> </Item>
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
); );
@@ -143,7 +143,7 @@ export const DropdownMenuGroup = forwardRef<HTMLDivElement, DropdownMenuGroupPro
({ ...props }, ref) => ( ({ ...props }, ref) => (
<DropdownMenuPrimitive.Group <DropdownMenuPrimitive.Group
{...props} {...props}
className={twMerge("text-xs py-2 pl-3", props.className)} className={twMerge("py-2 pl-3 text-xs", props.className)}
ref={ref} ref={ref}
/> />
) )
@@ -159,7 +159,7 @@ export const DropdownMenuSeparator = forwardRef<
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
ref={ref} ref={ref}
{...props} {...props}
className={twMerge("h-[1px] bg-gray-700 m-1", className)} className={twMerge("m-1 h-[1px] bg-gray-700", className)}
/> />
)); ));

View File

@@ -10,7 +10,8 @@ export const EmailServiceSetupModal = ({ isOpen, onOpenChange }: Props): JSX.Ele
<Modal isOpen={isOpen} onOpenChange={onOpenChange}> <Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent title="Email service not configured"> <ModalContent title="Email service not configured">
<p className="mb-4 text-bunker-300"> <p className="mb-4 text-bunker-300">
The administrators of this Infisical instance have not yet set up an email service provider required to perform this action The administrators of this Infisical instance have not yet set up an email service provider
required to perform this action
</p> </p>
<a href="https://infisical.com/docs/self-hosting/configuration/email"> <a href="https://infisical.com/docs/self-hosting/configuration/email">

View File

@@ -23,7 +23,7 @@ export const FormLabel = ({ id, label, isRequired, icon, className }: FormLabelP
{label} {label}
{isRequired && <span className="ml-1 text-red">*</span>} {isRequired && <span className="ml-1 text-red">*</span>}
{icon && ( {icon && (
<span className="ml-2 text-mineshaft-300 hover:text-mineshaft-200 cursor-default"> <span className="ml-2 cursor-default text-mineshaft-300 hover:text-mineshaft-200">
{icon} {icon}
</span> </span>
)} )}

View File

@@ -10,22 +10,16 @@ type Props = {
export type HoverCardProps = Props; export type HoverCardProps = Props;
export const HoverObject = ({ export const HoverObject = ({ text, icon, color }: Props): JSX.Element => (
text,
icon,
color
}: Props): JSX.Element => (
<HoverCard.Root openDelay={50}> <HoverCard.Root openDelay={50}>
<HoverCard.Trigger asChild> <HoverCard.Trigger asChild>
<a <a className="ImageTrigger z-20">
className="ImageTrigger z-20"
>
<FontAwesomeIcon icon={icon} className={`text-${color}`} /> <FontAwesomeIcon icon={icon} className={`text-${color}`} />
</a> </a>
</HoverCard.Trigger> </HoverCard.Trigger>
<HoverCard.Portal> <HoverCard.Portal>
<HoverCard.Content className="HoverCardContent z-[300]" sideOffset={5}> <HoverCard.Content className="HoverCardContent z-[300]" sideOffset={5}>
<div className='bg-bunker-700 border border-mineshaft-600 p-2 rounded-md drop-shadow-xl text-bunker-300'> <div className="rounded-md border border-mineshaft-600 bg-bunker-700 p-2 text-bunker-300 drop-shadow-xl">
<div style={{ display: "flex", flexDirection: "column", gap: 15 }}> <div style={{ display: "flex", flexDirection: "column", gap: 15 }}>
<div> <div>
<div className="Text bold">{text}</div> <div className="Text bold">{text}</div>

View File

@@ -1 +1 @@
export { HoverCard,HoverCardContent, HoverCardTrigger } from "./HoverCardv2"; export { HoverCard, HoverCardContent, HoverCardTrigger } from "./HoverCardv2";

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck // @ts-nocheck
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable global-require */ /* eslint-disable global-require */

View File

@@ -29,7 +29,7 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
<Card <Card
isRounded isRounded
className={twMerge( className={twMerge(
"fixed top-1/2 left-1/2 z-30 dark:[color-scheme:dark] max-h-screen thin-scrollbar max-w-xl -translate-y-2/4 -translate-x-2/4 animate-popIn border border-mineshaft-600 drop-shadow-2xl", "thin-scrollbar fixed top-1/2 left-1/2 z-30 max-h-screen max-w-xl -translate-y-2/4 -translate-x-2/4 animate-popIn border border-mineshaft-600 drop-shadow-2xl dark:[color-scheme:dark]",
className className
)} )}
> >

View File

@@ -44,7 +44,7 @@ export const Pagination = ({
return ( return (
<div <div
className={twMerge( className={twMerge(
"flex items-center justify-end text-white w-full py-3 px-4 bg-mineshaft-800", "flex w-full items-center justify-end bg-mineshaft-800 py-3 px-4 text-white",
className className
)} )}
> >

View File

@@ -11,31 +11,34 @@ type Props = {
export type PopoverProps = Props; export type PopoverProps = Props;
export const PopoverObject = ({children, text, onChangeHandler, id}: Props) => ( export const PopoverObject = ({ children, text, onChangeHandler, id }: Props) => (
<Popover.Root> <Popover.Root>
<Popover.Trigger asChild className='data-[state=open]:outline data-[state=open]:outline-primary data-[state=closed]:hover:outline data-[state=closed]:hover:outline-mineshaft-400'> <Popover.Trigger
asChild
className="data-[state=open]:outline data-[state=open]:outline-primary data-[state=closed]:hover:outline data-[state=closed]:hover:outline-mineshaft-400"
>
{children} {children}
</Popover.Trigger> </Popover.Trigger>
<Popover.Portal> <Popover.Portal>
<Popover.Content <Popover.Content
className="rounded z-[100] p-3 w-[460px] min-h-fit border border-chicago-700 bg-mineshaft-600 shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)] focus:shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2),0_0_0_2px_theme(colors.violet7)] will-change-[transform,opacity] data-[state=open]:data-[side=top]:animate-slideDownAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade" className="z-[100] min-h-fit w-[460px] rounded border border-chicago-700 bg-mineshaft-600 p-3 shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)] will-change-[transform,opacity] focus:shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2),0_0_0_2px_theme(colors.violet7)] data-[state=open]:data-[side=top]:animate-slideDownAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade"
sideOffset={5} sideOffset={5}
hideWhenDetached hideWhenDetached
side="left" side="left"
> >
<div className="flex flex-col pt-2 dark"> <div className="dark flex flex-col pt-2">
<p className="text-bunker-200 text-[15px] leading-[0px] font-medium mb-5">Comment</p> <p className="mb-5 text-[15px] font-medium leading-[0px] text-bunker-200">Comment</p>
<textarea <textarea
onChange={(e) => onChangeHandler(e.target.value, id)} onChange={(e) => onChangeHandler(e.target.value, id)}
// type={type} // type={type}
value={text} value={text}
className='z-10 dark:[color-scheme:dark] peer h-[20rem] ph-no-capture bg-bunker-600 border border-mineshaft-500 rounded-md py-2.5 caret-bunker-200 text-sm px-2 w-full outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200' className="ph-no-capture placeholder peer z-10 h-[20rem] w-full rounded-md border border-mineshaft-500 bg-bunker-600 py-2.5 px-2 text-sm text-bunker-300 caret-bunker-200 outline-none duration-200 placeholder:text-bunker-400 focus:text-bunker-100 placeholder:focus:text-transparent dark:[color-scheme:dark]"
spellCheck="false" spellCheck="false"
placeholder='' placeholder=""
/> />
</div> </div>
<Popover.Close <Popover.Close
className="rounded-full h-[25px] w-[25px] inline-flex items-center justify-center text-bunker-300 hover:text-white absolute top-[5px] right-[5px] hover:bg-violet4 focus:shadow-[0_0_0_2px] focus:shadow-violet7 outline-none cursor-default" className="hover:bg-violet4 focus:shadow-violet7 absolute top-[5px] right-[5px] inline-flex h-[25px] w-[25px] cursor-default items-center justify-center rounded-full text-bunker-300 outline-none hover:text-white focus:shadow-[0_0_0_2px]"
aria-label="Close" aria-label="Close"
> >
<FontAwesomeIcon icon={faXmark} /> <FontAwesomeIcon icon={faXmark} />

View File

@@ -1 +1 @@
export { Popover,PopoverContent, PopoverTrigger } from "./Popoverv2"; export { Popover, PopoverContent, PopoverTrigger } from "./Popoverv2";

View File

@@ -7,32 +7,32 @@ export type RadioGroupProps = RadioGroupPrimitive.RadioGroupProps;
// Note this component is not customizable (Heroku integration and potentially other pages depend on it) // Note this component is not customizable (Heroku integration and potentially other pages depend on it)
export const RadioGroup = ({ className, children, ...props }: RadioGroupProps) => ( export const RadioGroup = ({ className, children, ...props }: RadioGroupProps) => (
<RadioGroupPrimitive.Root <RadioGroupPrimitive.Root
className={twMerge("flex flex-row gap-5 px-6 mb-6", className)} className={twMerge("mb-6 flex flex-row gap-5 px-6", className)}
defaultValue="App" defaultValue="App"
aria-label="View density" aria-label="View density"
{...props} {...props}
> >
<div className="flex items-center"> <div className="flex items-center">
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
className="bg-bunker-400/20 w-[20px] h-[20px] rounded-full hover:bg-bunker-400/40 border border-bunker-400/60 duration-200 outline-none cursor-default" className="h-[20px] w-[20px] cursor-default rounded-full border border-bunker-400/60 bg-bunker-400/20 outline-none duration-200 hover:bg-bunker-400/40"
value="App" value="App"
id="r1" id="r1"
> >
<RadioGroupPrimitive.Indicator className="flex items-center justify-center w-full h-full relative after:content-[''] after:block after:w-[11px] after:h-[11px] after:rounded-[50%] after:bg-primary" /> <RadioGroupPrimitive.Indicator className="relative flex h-full w-full items-center justify-center after:block after:h-[11px] after:w-[11px] after:rounded-[50%] after:bg-primary after:content-['']" />
</RadioGroupPrimitive.Item> </RadioGroupPrimitive.Item>
<label className="text-bunker-200 text-sm leading-none pl-2" htmlFor="r1"> <label className="pl-2 text-sm leading-none text-bunker-200" htmlFor="r1">
App App
</label> </label>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
className="bg-bunker-400/20 w-[22px] h-[22px] rounded-full hover:bg-bunker-400/40 border border-bunker-400/60 duration-200 outline-none cursor-default" className="h-[22px] w-[22px] cursor-default rounded-full border border-bunker-400/60 bg-bunker-400/20 outline-none duration-200 hover:bg-bunker-400/40"
value="Pipeline" value="Pipeline"
id="r2" id="r2"
> >
<RadioGroupPrimitive.Indicator className="flex items-center justify-center w-full h-full relative after:content-[''] after:block after:w-[13px] after:h-[13px] after:rounded-[50%] after:bg-primary" /> <RadioGroupPrimitive.Indicator className="relative flex h-full w-full items-center justify-center after:block after:h-[13px] after:w-[13px] after:rounded-[50%] after:bg-primary after:content-['']" />
</RadioGroupPrimitive.Item> </RadioGroupPrimitive.Item>
<label className="text-bunker-200 text-sm leading-none pl-2" htmlFor="r2"> <label className="pl-2 text-sm leading-none text-bunker-200" htmlFor="r2">
Pipeline Pipeline
</label> </label>
</div> </div>

View File

@@ -14,19 +14,20 @@ const replaceContentWithDot = (str: string) => {
return finalStr; return finalStr;
}; };
const syntaxHighlight = (content?: string | null, isVisible?: boolean) => { const syntaxHighlight = (content?: string | null, isVisible?: boolean, isImport?: boolean) => {
if (isImport) return "IMPORTED";
if (content === "") return "EMPTY"; if (content === "") return "EMPTY";
if (!content) return "EMPTY"; if (!content) return "EMPTY";
if (!isVisible) return replaceContentWithDot(content); if (!isVisible) return replaceContentWithDot(content);
let skipNext = false; let skipNext = false;
const formatedContent = content.split(REGEX).flatMap((el, i) => { const formattedContent = content.split(REGEX).flatMap((el, i) => {
const isInterpolationSyntax = el.startsWith("${") && el.endsWith("}"); const isInterpolationSyntax = el.startsWith("${") && el.endsWith("}");
if (isInterpolationSyntax) { if (isInterpolationSyntax) {
skipNext = true; skipNext = true;
return ( return (
<span className="ph-no-capture text-yellow" key={`secret-value-${i + 1}`}> <span className="ph-no-capture text-yellow" key={`secret-value-${i + 1}`}>
&#36;&#123;<span className="ph-no-capture text-yello-200/80">{el.slice(2, -1)}</span> &#36;&#123;<span className="ph-no-capture text-yellow-200/80">{el.slice(2, -1)}</span>
&#125; &#125;
</span> </span>
); );
@@ -40,12 +41,13 @@ const syntaxHighlight = (content?: string | null, isVisible?: boolean) => {
// akhilmhdh: Dont remove this br. I am still clueless how this works but weirdly enough // akhilmhdh: Dont remove this br. I am still clueless how this works but weirdly enough
// when break is added a line break works properly // when break is added a line break works properly
return formatedContent.concat(<br />); return formattedContent.concat(<br />);
}; };
type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & { type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
value?: string | null; value?: string | null;
isVisible?: boolean; isVisible?: boolean;
isImport?: boolean;
isReadOnly?: boolean; isReadOnly?: boolean;
isDisabled?: boolean; isDisabled?: boolean;
containerClassName?: string; containerClassName?: string;
@@ -55,7 +57,17 @@ const commonClassName = "font-mono text-sm caret-white border-none outline-none
export const SecretInput = forwardRef<HTMLTextAreaElement, Props>( export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
( (
{ value, isVisible, containerClassName, onBlur, isDisabled, isReadOnly, onFocus, ...props }, {
value,
isVisible,
isImport,
containerClassName,
onBlur,
isDisabled,
isReadOnly,
onFocus,
...props
},
ref ref
) => { ) => {
const [isSecretFocused, setIsSecretFocused] = useToggle(); const [isSecretFocused, setIsSecretFocused] = useToggle();
@@ -69,7 +81,7 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
<pre aria-hidden className="m-0 "> <pre aria-hidden className="m-0 ">
<code className={`inline-block w-full ${commonClassName}`}> <code className={`inline-block w-full ${commonClassName}`}>
<span style={{ whiteSpace: "break-spaces" }}> <span style={{ whiteSpace: "break-spaces" }}>
{syntaxHighlight(value, isVisible || isSecretFocused)} {syntaxHighlight(value, isVisible || isSecretFocused, isImport)}
</span> </span>
</code> </code>
</pre> </pre>

View File

@@ -1,6 +1,6 @@
import { forwardRef, ReactNode } from "react"; import { forwardRef, ReactNode } from "react";
import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { faCaretDown, faCaretUp,faCheck } from "@fortawesome/free-solid-svg-icons"; import { faCaretDown, faCaretUp, faCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as SelectPrimitive from "@radix-ui/react-select"; import * as SelectPrimitive from "@radix-ui/react-select";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -41,7 +41,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
ref={ref} ref={ref}
className={twMerge( className={twMerge(
`inline-flex items-center justify-between rounded-md `inline-flex items-center justify-between rounded-md
bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200`, bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200 focus:bg-mineshaft-700/80`,
className className
)} )}
> >
@@ -57,7 +57,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
<SelectPrimitive.Portal> <SelectPrimitive.Portal>
<SelectPrimitive.Content <SelectPrimitive.Content
className={twMerge( className={twMerge(
"relative top-1 z-[100] overflow-hidden rounded-md bg-mineshaft-900 border border-mineshaft-600 font-inter text-bunker-100 shadow-md", "relative top-1 z-[100] overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 font-inter text-bunker-100 shadow-md",
dropdownContainerClassName dropdownContainerClassName
)} )}
position={position} position={position}
@@ -106,7 +106,7 @@ export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
className={twMerge( className={twMerge(
`relative mb-0.5 flex `relative mb-0.5 flex
cursor-pointer select-none items-center rounded-md py-2 pl-10 pr-4 text-sm cursor-pointer select-none items-center rounded-md py-2 pl-10 pr-4 text-sm
outline-none transition-all hover:bg-mineshaft-500`, outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-700/80`,
isSelected && "bg-primary", isSelected && "bg-primary",
isDisabled && isDisabled &&
"cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-mineshaft-600", "cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-mineshaft-600",

View File

@@ -20,7 +20,7 @@ export const Spinner = ({ className, size = "md" }: Props): JSX.Element => {
<svg <svg
aria-hidden="true" aria-hidden="true"
className={twMerge( className={twMerge(
"text-gray-200 animate-spin dark:text-gray-600 fill-primary m-1", "m-1 animate-spin fill-primary text-gray-200 dark:text-gray-600",
sizeChart[size], sizeChart[size],
className className
)} )}

View File

@@ -14,7 +14,7 @@ export const Stepper = ({ activeStep, children, direction, className }: StepperP
return ( return (
<div <div
className={twMerge( className={twMerge(
"flex items-center w-full space-x-3 p-2 border border-bunker-300/30 rounded-md", "flex w-full items-center space-x-3 rounded-md border border-bunker-300/30 p-2",
className className
)} )}
> >
@@ -25,15 +25,15 @@ export const Stepper = ({ activeStep, children, direction, className }: StepperP
return ( return (
<div <div
className={twMerge( className={twMerge(
"flex items-center space-x-3 flex-shrink-0", "flex flex-shrink-0 items-center space-x-3",
isNotLast && "flex-grow" isNotLast && "flex-grow"
)} )}
> >
<div className="flex items-center space-x-2 flex-shrink-0"> <div className="flex flex-shrink-0 items-center space-x-2">
<div <div
className={twMerge( className={twMerge(
"w-7 h-7 flex items-center justify-center font-medium text-mineshaft-800 text-sm rounded-full transition-all", "flex h-7 w-7 items-center justify-center rounded-full text-sm font-medium text-mineshaft-800 transition-all",
isCompleted ? "bg-primary" : "border text-bunker-300 border-primary/30", isCompleted ? "bg-primary" : "border border-primary/30 text-bunker-300",
isActive && "bg-primary text-mineshaft-800" isActive && "bg-primary text-mineshaft-800"
)} )}
> >
@@ -71,7 +71,7 @@ export type StepProps = {
export const Step = ({ title, description }: StepProps) => { export const Step = ({ title, description }: StepProps) => {
return ( return (
<div className="flex flex-col text-gray-300"> <div className="flex flex-col text-gray-300">
<div className="font-medium text-sm">{title}</div> <div className="text-sm font-medium">{title}</div>
{description && <div className="text-xs">{description}</div>} {description && <div className="text-xs">{description}</div>}
</div> </div>
); );

View File

@@ -1,2 +1,2 @@
export type { StepperProps,StepProps } from "./Stepper"; export type { StepperProps, StepProps } from "./Stepper";
export { Step,Stepper } from "./Stepper"; export { Step, Stepper } from "./Stepper";

View File

@@ -6,5 +6,6 @@ export type {
TFootProps, TFootProps,
THeadProps, THeadProps,
ThProps, ThProps,
TrProps} from "./Table"; TrProps
export { Table, TableContainer, TableSkeleton, TBody, Td, TFoot,Th, THead, Tr } from "./Table"; } from "./Table";
export { Table, TableContainer, TableSkeleton, TBody, Td, TFoot, Th, THead, Tr } from "./Table";

View File

@@ -13,7 +13,7 @@ export type TabListProps = TabsPrimitive.TabsListProps;
export const TabList = ({ className, children, ...props }: TabListProps) => ( export const TabList = ({ className, children, ...props }: TabListProps) => (
<TabsPrimitive.List <TabsPrimitive.List
className={twMerge("flex-shrink-0 flex border-b-2 border-mineshaft-800", className)} className={twMerge("flex flex-shrink-0 border-b-2 border-mineshaft-800", className)}
{...props} {...props}
> >
{children} {children}
@@ -25,7 +25,7 @@ export type TabProps = TabsPrimitive.TabsTriggerProps;
export const Tab = ({ className, children, ...props }: TabProps) => ( export const Tab = ({ className, children, ...props }: TabProps) => (
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
className={twMerge( className={twMerge(
"px-3 h-10 font-medium text-sm flex items-center justify-center select-none first:rounded-tl-md last:rounded-tr-md hover:text-mineshaft-200 text-mineshaft-400 transition-all data-[state=active]:text-white data-[state=active]:border-b data-[state=active]:border-primary", "flex h-10 select-none items-center justify-center px-3 text-sm font-medium text-mineshaft-400 transition-all first:rounded-tl-md last:rounded-tr-md hover:text-mineshaft-200 data-[state=active]:border-b data-[state=active]:border-primary data-[state=active]:text-white",
className className
)} )}
{...props} {...props}
@@ -38,7 +38,7 @@ export type TabPanelProps = TabsPrimitive.TabsContentProps;
export const TabPanel = ({ className, children, ...props }: TabPanelProps) => ( export const TabPanel = ({ className, children, ...props }: TabPanelProps) => (
<TabsPrimitive.Content <TabsPrimitive.Content
className={twMerge("outline-none flex-grow py-5 rounded-bl-md rounded-br-md", className)} className={twMerge("flex-grow rounded-bl-md rounded-br-md py-5 outline-none", className)}
{...props} {...props}
> >
{children} {children}

View File

@@ -1,2 +1,2 @@
export type { TabListProps,TabPanelProps, TabProps, TabsProps } from "./Tabs"; export type { TabListProps, TabPanelProps, TabProps, TabsProps } from "./Tabs";
export { Tab, TabList, TabPanel, Tabs } from "./Tabs"; export { Tab, TabList, TabPanel, Tabs } from "./Tabs";

View File

@@ -11,6 +11,8 @@ export type TooltipProps = Omit<TooltipPrimitive.TooltipContentProps, "open" | "
onOpenChange?: (isOpen: boolean) => void; onOpenChange?: (isOpen: boolean) => void;
defaultOpen?: boolean; defaultOpen?: boolean;
position?: "top" | "bottom" | "left" | "right"; position?: "top" | "bottom" | "left" | "right";
isDisabled?: boolean;
center?: boolean;
}; };
export const Tooltip = ({ export const Tooltip = ({
@@ -20,7 +22,9 @@ export const Tooltip = ({
onOpenChange, onOpenChange,
defaultOpen, defaultOpen,
className, className,
center,
asChild = true, asChild = true,
isDisabled,
position = "top", position = "top",
...props ...props
}: TooltipProps) => ( }: TooltipProps) => (
@@ -38,11 +42,13 @@ export const Tooltip = ({
{...props} {...props}
className={twMerge( className={twMerge(
`z-50 max-w-[15rem] select-none rounded-md border border-mineshaft-600 bg-mineshaft-800 py-2 px-4 text-sm font-light text-bunker-200 shadow-md `z-50 max-w-[15rem] select-none rounded-md border border-mineshaft-600 bg-mineshaft-800 py-2 px-4 text-sm font-light text-bunker-200 shadow-md
data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade
data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade
data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade
data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade
`, `,
isDisabled && "!hidden",
center && "text-center",
className className
)} )}
> >

Some files were not shown because too many files have changed in this diff Show More