mirror of
https://github.com/Infisical/infisical.git
synced 2025-04-13 01:49:57 +00:00
Compare commits
201 Commits
Author | SHA1 | Date | |
---|---|---|---|
1808ab6db8 | |||
aa554405c1 | |||
f49fe3962d | |||
9ee0c8f1b7 | |||
059f15b172 | |||
caddb45394 | |||
8266c4dd6d | |||
3d25baa319 | |||
a8dfcae777 | |||
228c8a7609 | |||
b25908d91f | |||
68d51d402a | |||
aa218d2ddc | |||
c36aa3591a | |||
58b252a9e9 | |||
dba4c03e37 | |||
7b1be82bac | |||
3c449750d3 | |||
006f61a0e8 | |||
ec85bfca04 | |||
27353848c1 | |||
da6a8ccdea | |||
b5a4e42281 | |||
1212b5a9db | |||
d99e21a91b | |||
112fc77a06 | |||
c23be77738 | |||
0cea019bc2 | |||
1218dc09ed | |||
b89a221a5a | |||
c4124cc865 | |||
94b7a0aead | |||
689f1d0d43 | |||
c75c24d44e | |||
febdf48dea | |||
688b383d8b | |||
9436f40eac | |||
d45eff621b | |||
761a60a216 | |||
0466bf4e3d | |||
16a1366e6c | |||
b06c8d241e | |||
7bbaf4fee8 | |||
91c8fd14df | |||
9e1112eb52 | |||
6050e65a59 | |||
bbc23aca55 | |||
9098bdc751 | |||
251426b559 | |||
793eaee0c6 | |||
349865e6ef | |||
add3075439 | |||
3db1ff2411 | |||
8cd9e20fa6 | |||
29ab13430f | |||
90eb292721 | |||
5e1f6d3884 | |||
1310b176a9 | |||
ccf1010e94 | |||
d46bf54a8d | |||
93703475fe | |||
991b10cc17 | |||
60fcd34af5 | |||
f60e0cf7ee | |||
9071fafd06 | |||
9499aa1097 | |||
389bf0b41f | |||
6ed5b9e706 | |||
209673d744 | |||
baacc310bb | |||
1e16a18469 | |||
df1ade4f5f | |||
944cc5b32c | |||
a83d536ea4 | |||
1454911085 | |||
9e73b3431e | |||
4a105a72e9 | |||
a47decd31f | |||
c5a422fe64 | |||
bb47f7a92f | |||
13f2ab9425 | |||
ac2c50b161 | |||
afb374ff13 | |||
e98b76cba5 | |||
3e2ed62e50 | |||
8e15dfc3d9 | |||
6fb22b68dd | |||
05a19a2201 | |||
9ee5f3d41b | |||
142a38ae3c | |||
e67620c3ce | |||
e8e6b72422 | |||
4cc4edcb7e | |||
2acea4f085 | |||
0dd546813a | |||
d82dfa5504 | |||
b13b0693ba | |||
e00c3ab9e2 | |||
088668e1b0 | |||
b21cb521da | |||
90ef67399b | |||
5e87a317fa | |||
6cdbc834ab | |||
b677ab6429 | |||
3b002abcb6 | |||
89c625750a | |||
3de79426e2 | |||
02284f350b | |||
0e011da41c | |||
4fca41f62c | |||
f7044d37cb | |||
2299cff7d8 | |||
9c66062e6e | |||
127f77d1ce | |||
040fa815df | |||
0eff4a7389 | |||
43bf99e659 | |||
3bb3fd3531 | |||
2a1cb7c00d | |||
74467320cb | |||
86b12b16bf | |||
a5d509c541 | |||
249635f0cc | |||
20d8d255cb | |||
57762ab73c | |||
656d375af0 | |||
f5035a4169 | |||
5f2d3056f1 | |||
fa41baa8b6 | |||
fe15af4c28 | |||
986a9449cf | |||
de7e5016dd | |||
8bbd5a1184 | |||
212f1feeb6 | |||
4ae88b2e47 | |||
43fb35381f | |||
023c744a8e | |||
da419361cb | |||
42087923e0 | |||
906cedd168 | |||
f659be446d | |||
63c4cfa651 | |||
adf27351a3 | |||
c1d4002551 | |||
1c56c34211 | |||
d8aa5b5ff4 | |||
a5618681df | |||
a84fc847db | |||
f8e7c3c7c6 | |||
f9bf418bf8 | |||
7950085fba | |||
9e0860b9a6 | |||
c3427d110a | |||
0fde680a11 | |||
ef248e3944 | |||
c940e1ad16 | |||
aa700f7670 | |||
f30da163d8 | |||
1f63454f8d | |||
8e2794f6ab | |||
6468b356a6 | |||
c6777e43ed | |||
c68eaa613c | |||
00dde5c2b4 | |||
ad19e33638 | |||
4117781cd1 | |||
11d169ad23 | |||
91827aed3e | |||
fe339d9c0f | |||
e818029b48 | |||
4f5ad07ace | |||
3ece5a0390 | |||
078dbde45a | |||
8953fdf1d8 | |||
bccee0c94d | |||
7447288e5c | |||
7ab2289c99 | |||
1b07199383 | |||
3c7cd01dd5 | |||
4cfb275186 | |||
0a9f4ffc4d | |||
e18a44f723 | |||
62c2be255d | |||
3246d6b6df | |||
5f670cd104 | |||
6722bd7bea | |||
56acc4f888 | |||
798eb67296 | |||
2fe45ec898 | |||
4d0969fbc3 | |||
c4711fc328 | |||
938c7bdb93 | |||
634d5fe5c3 | |||
1961b92340 | |||
ef234a270f | |||
66d2a2724e | |||
2729b409e6 | |||
f5d2836199 | |||
065b37ac11 | |||
ca3b2fa1a0 | |||
4ea284a1c5 |
.github
images
workflows
backend
cli/packages
docker-compose.dev.ymldocker-compose.ymldocs
cli
contributing
getting-started
images
integrations
cloud
frameworks
django.mdxexpress.mdxfiber.mdxflask.mdxgatsby.mdxlaravel.mdxnestjs.mdxnextjs.mdxnuxt.mdxrails.mdxreact.mdxremix.mdxvite.mdxvue.mdx
heroku.mdxoverview.mdxplatforms
self-hosting
frontend
.eslintrc.prettierrcDockerfileREADME.md
components
RouteGuard.js
const.jsjsconfig.jsonnext-env.d.tsnext.config.jspackage-lock.jsonpackage.jsonanalytics
basic
Error.jsError.tsxInputField.jsInputField.tsxLayout.tsxListbox.jsListbox.tsx
buttons
dialog
AddIncidentContactDialog.jsAddProjectMemberDialog.jsAddServiceTokenDialog.jsAddUserDialog.jsAddWorkspaceDialog.jsDeleteUserDialog.js
layout.jspopups
table
billing
context/Notifications
dashboard
navigation
utilities
SecurityClient.jsattemptLogin.js
checks
config
cryptography
file.jsgenerateBackupPDF.jsrandomId.jssaveTokenToLocalStorage.tssecrets
telemetry
pages
_app.jsdashboard.js
postcss.config.jsapi
auth
ChangePassword2.jsCheckAuth.jsCheckEmailVerificationCode.jsCompleteAccountInformationSignup.jsCompleteAccountInformationSignupInvite.jsIssueBackupPrivateKey.jsLogin1.jsLogin1.tsLogin2.jsLogin2.tsLogout.jsLogout.tsSRP1.jsSendVerificationEmail.jsToken.jsVerifySignupInvite.jspublicKeyInfisical.js
files
integrations
ChangeHerokuConfigVars.jsDeleteIntegration.jsDeleteIntegrationAuth.jsGetIntegrationApps.jsGetIntegrations.jsStartIntegration.jsauthorizeIntegration.jsgetWorkspaceAuthorizations.jsgetWorkspaceIntegrations.js
organization
GetOrg.jsGetOrg.tsGetOrgProjects.jsGetOrgSubscription.jsGetOrgUserProjects.jsGetOrgUsers.jsGetOrgUsers.tsStripeRedirect.jsaddIncidentContact.jsaddUserToOrg.jsdeleteIncidentContact.jsdeleteUserFromOrganization.jsgetIncidentContacts.jsgetOrgs.jsgetOrgs.tsrenameOrg.js
serviceToken
user
userActions
workspace
addUserToWorkspace.jschangeUserRoleInWorkspace.jscreateWorkspace.jscreateWorkspace.tsdeleteUserFromWorkspace.jsdeleteWorkspace.jsgetLatestFileKey.jsgetLatestFileKey.tsgetProjectInfo.tsgetWorkspaceInfo.jsgetWorkspaceKeys.jsgetWorkspaceKeys.tsgetWorkspaceUsers.jsgetWorkspaceUsers.tsgetWorkspaces.jsgetWorkspaces.tsrenameWorkspace.jsuploadKeys.js
dashboard
heroku.jshome
index.jsintegrations
login.jsnoprojects.jsrequestnewinvite.jssettings
signup.jssignup.tsxsignupinvite.jsusers
public
data
images
scripts
tailwind.config.jstsconfig.jsonhelm-charts
img
BIN
.github/images/star-infisical.gif
vendored
BIN
.github/images/star-infisical.gif
vendored
Binary file not shown.
Before ![]() (image error) Size: 132 KiB After ![]() (image error) Size: 106 KiB ![]() ![]() |
41
.github/workflows/check-be-pull-request.yml
vendored
Normal file
41
.github/workflows/check-be-pull-request.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Check Backend Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize ]
|
||||
paths:
|
||||
- 'backend/**'
|
||||
- '!backend/README.md'
|
||||
- '!backend/.*'
|
||||
- 'backend/.eslintrc.js'
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
check-be-pr:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
-
|
||||
name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: 🔧 Setup Node 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: backend/package-lock.json
|
||||
-
|
||||
name: 📦 Install dependencies
|
||||
run: npm ci --only-production --ignore-scripts
|
||||
working-directory: backend
|
||||
# -
|
||||
# name: 🧪 Run tests
|
||||
# run: npm run test:ci
|
||||
# working-directory: backend
|
||||
-
|
||||
name: 🏗️ Run build
|
||||
run: npm run build
|
||||
working-directory: backend
|
41
.github/workflows/check-fe-pull-request.yml
vendored
Normal file
41
.github/workflows/check-fe-pull-request.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Check Frontend Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize ]
|
||||
paths:
|
||||
- 'frontend/**'
|
||||
- '!frontend/README.md'
|
||||
- '!frontend/.*'
|
||||
- 'frontend/.eslintrc.js'
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
check-fe-pr:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
-
|
||||
name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: 🔧 Setup Node 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
-
|
||||
name: 📦 Install dependencies
|
||||
run: npm ci --only-production --ignore-scripts
|
||||
working-directory: frontend
|
||||
# -
|
||||
# name: 🧪 Run tests
|
||||
# run: npm run test:ci
|
||||
# working-directory: frontend
|
||||
-
|
||||
name: 🏗️ Run build
|
||||
run: npm run build
|
||||
working-directory: frontend
|
22
.github/workflows/close_inactive_issues.yml
vendored
Normal file
22
.github/workflows/close_inactive_issues.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v4
|
||||
with:
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
72
.github/workflows/docker-image.yml
vendored
72
.github/workflows/docker-image.yml
vendored
@ -3,32 +3,88 @@ name: Push to Docker Hub
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
|
||||
backend-image:
|
||||
name: Build backend image
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
-
|
||||
name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up QEMU
|
||||
name: 🔧 Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
name: 🔧 Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
name: 🐋 Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
# -
|
||||
# name: 📦 Build backend and export to Docker
|
||||
# uses: docker/build-push-action@v3
|
||||
# with:
|
||||
# load: true
|
||||
# context: backend
|
||||
# tags: infisical/backend:test
|
||||
# -
|
||||
# name: 🧪 Test backend image
|
||||
# run: |
|
||||
# docker run --rm infisical/backend:test
|
||||
-
|
||||
name: Build and push backend
|
||||
name: 🏗️ Build backend and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
push: true
|
||||
context: /backend
|
||||
context: backend
|
||||
tags: infisical/backend:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
|
||||
frontend-image:
|
||||
name: Build frontend image
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
-
|
||||
name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Build and push frontend
|
||||
name: 🔧 Set up QEMU
|
||||
uses: docker/setup-qemu-action@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: 📦 Build frontend and export to Docker
|
||||
# uses: docker/build-push-action@v3
|
||||
# with:
|
||||
# load: true
|
||||
# context: frontend
|
||||
# tags: infisical/frontend:test
|
||||
# build-args: |
|
||||
# POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||
# -
|
||||
# name: 🧪 Test frontend image
|
||||
# run: |
|
||||
# docker run --rm infisical/frontend:test
|
||||
-
|
||||
name: 🏗️ Build frontend and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
push: true
|
||||
context: /frontend
|
||||
context: frontend
|
||||
tags: infisical/frontend:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||
|
22
.github/workflows/helm_chart_release.yml
vendored
Normal file
22
.github/workflows/helm_chart_release.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: Release Helm Charts
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
with:
|
||||
version: v3.10.0
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v4
|
||||
- name: Install Cloudsmith CLI
|
||||
run: pip install --upgrade cloudsmith-cli
|
||||
- name: Build and push helm package to Cloudsmith
|
||||
run: cd helm-charts && sh upload-to-cloudsmith.sh
|
||||
env:
|
||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
2
.github/workflows/release_build.yml
vendored
2
.github/workflows/release_build.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: goreleaser
|
||||
name: Go releaser
|
||||
|
||||
on:
|
||||
push:
|
||||
|
238
README.md
238
README.md
@ -1,5 +1,5 @@
|
||||
<h1 align="center">
|
||||
<img width="300" src="/img/logoname-black.svg#gh-light-mode-only" alt="ifnisical">
|
||||
<img width="300" src="/img/logoname-black.svg#gh-light-mode-only" alt="infisical">
|
||||
<img width="300" src="/img/logoname-white.svg#gh-dark-mode-only" alt="infisical">
|
||||
</h1>
|
||||
<p align="center">
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
<h4 align="center">
|
||||
<a href="https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g">Slack</a> |
|
||||
<a href="https://infisical.com/signup">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/getting-started/introduction">Docs</a> |
|
||||
<a href="https://www.infisical.com">Website</a>
|
||||
@ -21,6 +21,9 @@
|
||||
<a href="https://github.com/infisical/infisical/blob/main/CONTRIBUTING.md">
|
||||
<img src="https://img.shields.io/badge/PRs-Welcome-brightgreen" alt="PRs welcome!" />
|
||||
</a>
|
||||
<a href="">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/infisical/infisical" alt="git commit activity" />
|
||||
</a>
|
||||
<a href="https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g">
|
||||
<img src="https://img.shields.io/badge/chat-on%20Slack-blueviolet" alt="Slack community channel" />
|
||||
</a>
|
||||
@ -30,12 +33,12 @@
|
||||
|
||||
**[Infisical](https://infisical.com)** is an open source, E2EE tool to help teams manage and sync environment variables across their development workflow and infrastructure. It's designed to be simple and take minutes to get going.
|
||||
|
||||
- **User-Friendly Dashboard** to manage your team's environment variables within projects
|
||||
- **[User-Friendly Dashboard](https://infisical.com/docs/getting-started/dashboard/project)** to manage your team's environment variables within projects
|
||||
- **[Language-Agnostic CLI](https://infisical.com/docs/cli/overview)** that pulls and injects environment variables into your local workflow
|
||||
- **[Complete control over your data](https://infisical.com/docs/self-hosting/overview)** - host it yourself on any infrastructure
|
||||
- **Navigate Multiple Environments** per project (e.g. development, staging, production, etc.)
|
||||
- **Personal/Shared** scoping for environment variables
|
||||
- **[Integrations](https://infisical.com/docs/integrations/heroku)** with CI/CD and production infrastructure (Heroku available, more coming soon)
|
||||
- **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure (Heroku available, more coming soon)
|
||||
- 🔜 **1-Click Deploy** to Digital Ocean and Heroku
|
||||
- 🔜 **Authentication/Authorization** for projects (read/write controls soon)
|
||||
- 🔜 **Automatic Secret Rotation**
|
||||
@ -45,35 +48,42 @@
|
||||
|
||||
And more.
|
||||
|
||||
## Get started
|
||||
## 🚀 Get started
|
||||
|
||||
To quickly get started, visit our [get started guide](https://infisical.com/docs/getting-started/introduction).
|
||||
|
||||
## What's cool about this?
|
||||
<p>
|
||||
<a href="https://infisical.com/docs/self-hosting/overview" target="_blank"><img src="https://user-images.githubusercontent.com/78047717/206356882-2b773eed-b0da-4725-ae2f-83e3cd7f2713.png" height=120 /> </a>
|
||||
<a href="https://www.youtube.com/watch?v=JS3OKYU2078" target="_blank"><img src="https://user-images.githubusercontent.com/78047717/206356600-8833b128-6cae-408c-a703-07b2fc6aff4b.png" height=120 /> </a>
|
||||
<a href="https://app.infisical.com/signup" target="_blank"><img src="https://user-images.githubusercontent.com/78047717/206355970-f4c09062-b88f-452a-94e0-9c61a0651170.png" height=120></a>
|
||||
</p>
|
||||
|
||||
Infisical is simple, E2EE, and (soon to be) complete.
|
||||
## 🔥 What's cool about this?
|
||||
|
||||
Infisical makes secret management simple and end-to-end encrypted by default. We're on a mission to make it more accessible to all developers, <i>not just security teams</i>.
|
||||
|
||||
According to a [report](https://www.ekransystem.com/en/blog/secrets-management) in 2019, only 10% of organizations use secret management solutions despite all using digital secrets to some extent.
|
||||
|
||||
We're on a mission to make secret management more accessible to everyone — that means building for developers, not just security teams.
|
||||
|
||||
If you care about efficiency and security, then Infisical is right for you.
|
||||
|
||||
Need any integrations or want a new feature? Feel free to [create an issue](https://github.com/Infisical/infisical/issues) or [contribute](https://infisical.com/docs/contributing/overview) directly to the repository.
|
||||
We are currently working hard to make Infisical more extensive. Need any integrations or want a new feature? Feel free to [create an issue](https://github.com/Infisical/infisical/issues) or [contribute](https://infisical.com/docs/contributing/overview) directly to the repository.
|
||||
|
||||
## Contributing
|
||||
## 🌱 Contributing
|
||||
|
||||
Whether it's big or small, we love contributions ❤️ Check out our guide to see how to [get started](https://infisical.com/docs/contributing/overview).
|
||||
|
||||
Not sure where to get started? [Book a free, non-pressure pairing sessions with one of our teammates](mailto:tony@infisical.com?subject=Pairing%20session&body=I'd%20like%20to%20do%20a%20pairing%20session!)!
|
||||
Not sure where to get started? You can:
|
||||
- [Book a free, non-pressure pairing sessions with one of our teammates](mailto:tony@infisical.com?subject=Pairing%20session&body=I'd%20like%20to%20do%20a%20pairing%20session!)!
|
||||
- Join our <a href="https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g">Slack</a>, and ask us any questions there.
|
||||
|
||||
## Community & Support
|
||||
## 💚 Community & Support
|
||||
|
||||
- [GitHub Discussions](https://github.com/Infisical/infisical/discussions) for help with building and discussion.
|
||||
- [GitHub Issues](https://github.com/Infisical/infisical-cli/issues) for any bugs and errors you encounter using Infisical.
|
||||
- [Community Slack](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g) for hanging out with the community and quick communication with the team.
|
||||
- [Slack](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g) (For live discussion with the community and the Infisical team)
|
||||
- [GitHub Discussions](https://github.com/Infisical/infisical/discussions) (For help with building and deeper conversations about features)
|
||||
- [GitHub Issues](https://github.com/Infisical/infisical-cli/issues) (For any bugs and errors you encounter using Infisical)
|
||||
- [Twitter](https://twitter.com/infisical) (Get news fast)
|
||||
|
||||
## Status
|
||||
## 🐥 Status
|
||||
|
||||
- [x] Public Alpha: Anyone can sign up over at [infisical.com](https://infisical.com) but go easy on us, there are kinks and we're just getting started.
|
||||
- [ ] Public Beta: Stable enough for most non-enterprise use-cases.
|
||||
@ -81,37 +91,193 @@ Not sure where to get started? [Book a free, non-pressure pairing sessions with
|
||||
|
||||
We're currently in Public Alpha.
|
||||
|
||||
## Stay Up-to-Date
|
||||
## 🚨 Stay Up-to-Date
|
||||
|
||||
Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of new features are coming very quickly. Watch **releases** of this repository to be notified about future updates:
|
||||
|
||||

|
||||
|
||||
## Integrations
|
||||
## 🔌 Integrations
|
||||
|
||||
We're currently setting the foundation and building integrations so secrets can be synced everywhere. Any help is welcome! :)
|
||||
We're currently setting the foundation and building [integrations](https://infisical.com/docs/integrations/overview) so secrets can be synced everywhere. Any help is welcome! :)
|
||||
|
||||
- [x] [Docker](https://infisical.com/docs/integrations/docker)
|
||||
- [x] [Docker Compose](https://infisical.com/docs/integrations/docker-compose)
|
||||
- [x] [Heroku](https://infisical.com/docs/integrations/heroku)
|
||||
- [ ] Vercel
|
||||
- [ ] Kubernetes
|
||||
- [ ] AWS
|
||||
- [ ] GCP
|
||||
- [ ] Azure
|
||||
- [ ] Digital Ocean
|
||||
- [ ] GitLab
|
||||
- [ ] CircleCI
|
||||
<table>
|
||||
<tr>
|
||||
<th>Platforms </th>
|
||||
<th>Frameworks</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
## Open-source vs. paid
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/platforms/docker?ref=github.com">
|
||||
✔️ Docker
|
||||
</a>
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/platforms/docker-compose?ref=github.com">
|
||||
✔️ Docker Compose
|
||||
</a>
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/cloud/heroku?ref=github.com">
|
||||
✔️ Heroku
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
🔜 Vercel (https://github.com/Infisical/infisical/issues/60)
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
🔜 GitLab CI/CD
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
🔜 Fly.io
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
🔜 AWS
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
🔜 GitHub Actions (https://github.com/Infisical/infisical/issues/54)
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
🔜 Railway
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
🔜 GCP
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
🔜 Kubernetes
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
🔜 CircleCI
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
🔜 Jenkins
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
🔜 Digital Ocean
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
🔜 Azure
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
🔜 TravisCI
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
🔜 Netlify (https://github.com/Infisical/infisical/issues/55)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
This repo is entirely MIT licensed, with the exception of the `ee` directory which will contain premium enterprise features requring a Infisical license in the future. We're currently focused on developing non-enterprise offerings first that should suit most use-cases.
|
||||
</td>
|
||||
<td>
|
||||
|
||||
## Security
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/react?ref=github.com">
|
||||
✔️ React
|
||||
</a>
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/express?ref=github.com">
|
||||
✔️ Express
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/gatsby?ref=github.com">
|
||||
✔️ Gatsby
|
||||
</a>
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/flask?ref=github.com">
|
||||
✔️ Flask
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/django?ref=github.com">
|
||||
✔️ Django
|
||||
</a>
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/laravel?ref=github.com">
|
||||
✔️ Laravel
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/nestjs?ref=github.com">
|
||||
✔️ NestJS
|
||||
</a>
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/remix?ref=github.com">
|
||||
✔️ Remix
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/nextjs?ref=github.com">
|
||||
✔️ Next.js
|
||||
</a>
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/vite?ref=github.com">
|
||||
✔️ Vite
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/rails?ref=github.com">
|
||||
✔️ Ruby on Rails
|
||||
</a>
|
||||
</td>
|
||||
<td align="left" valign="middle">
|
||||
<a href="https://infisical.com/docs/integrations/frameworks/vue?ref=github.com">
|
||||
✔️ Vue
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## 🏘 Open-source vs. paid
|
||||
|
||||
This repo is entirely MIT licensed, with the exception of the `ee` directory which will contain premium enterprise features requiring a Infisical license in the future. We're currently focused on developing non-enterprise offerings first that should suit most use-cases.
|
||||
|
||||
## 🛡 Security
|
||||
|
||||
Looking to report a security vulnerability? Please don't post about it in GitHub issue. Instead, refer to our [SECURITY.md](./SECURITY.md) file.
|
||||
|
||||
## Contributors 🦸
|
||||
## 🦸 Contributors
|
||||
|
||||
[//]: contributor-faces
|
||||
|
||||
@ -119,4 +285,4 @@ Looking to report a security vulnerability? Please don't post about it in GitHub
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
|
||||
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a>
|
||||
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a>
|
||||
|
246
backend/package-lock.json
generated
246
backend/package-lock.json
generated
@ -10,7 +10,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sentry/node": "^7.14.0",
|
||||
"@sentry/tracing": "^7.14.0",
|
||||
"@sentry/tracing": "^7.19.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"axios": "^1.1.3",
|
||||
"bigint-conversion": "^2.2.2",
|
||||
@ -19,13 +19,13 @@
|
||||
"crypto-js": "^4.1.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "^4.18.1",
|
||||
"express-rate-limit": "^6.5.1",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"express-validator": "^6.14.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"helmet": "^5.1.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jsrp": "^0.2.4",
|
||||
"mongoose": "^6.7.1",
|
||||
"mongoose": "^6.7.2",
|
||||
"nodemailer": "^6.8.0",
|
||||
"posthog-node": "^2.1.0",
|
||||
"query-string": "^7.1.1",
|
||||
@ -33,7 +33,7 @@
|
||||
"stripe": "^10.7.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1",
|
||||
"typescript": "^4.8.4"
|
||||
"typescript": "^4.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@posthog/plugin-scaffold": "^1.3.4",
|
||||
@ -43,6 +43,8 @@
|
||||
"@types/jsonwebtoken": "^8.5.9",
|
||||
"@types/node": "^18.11.3",
|
||||
"@types/nodemailer": "^6.4.6",
|
||||
"@types/swagger-jsdoc": "^6.0.1",
|
||||
"@types/swagger-ui-express": "^4.1.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
||||
"@typescript-eslint/parser": "^5.40.1",
|
||||
"eslint": "^8.26.0",
|
||||
@ -2606,13 +2608,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/node": {
|
||||
"version": "7.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.17.4.tgz",
|
||||
"integrity": "sha512-cR+Gsir9c/tzFWxvk4zXkMQy6tNRHEYixHrb88XIjZVYDqDS9l2/bKs5nJusdmaUeLtmPp5Et2o7RJyS7gvKTQ==",
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.19.0.tgz",
|
||||
"integrity": "sha512-yG7Tx32WqOkEHVotFLrumCcT9qlaSDTkFNZ+yLSvZXx74ifsE781DzBA9W7K7bBdYO3op+p2YdsOKzf3nPpAyQ==",
|
||||
"dependencies": {
|
||||
"@sentry/core": "7.17.4",
|
||||
"@sentry/types": "7.17.4",
|
||||
"@sentry/utils": "7.17.4",
|
||||
"@sentry/core": "7.19.0",
|
||||
"@sentry/types": "7.19.0",
|
||||
"@sentry/utils": "7.19.0",
|
||||
"cookie": "^0.4.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"lru_map": "^0.3.3",
|
||||
@ -2622,14 +2624,80 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/tracing": {
|
||||
"version": "7.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.17.4.tgz",
|
||||
"integrity": "sha512-9Fz6DI16ddnd970mlB5MiCNRSmSXp4SVZ1Yv3L22oS3kQeNxjBTE+htYNwJzSPrQp9aL/LqTYwlnrCy24u9XQA==",
|
||||
"node_modules/@sentry/node/node_modules/@sentry/core": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.19.0.tgz",
|
||||
"integrity": "sha512-YF9cTBcAnO4R44092BJi5Wa2/EO02xn2ziCtmNgAVTN2LD31a/YVGxGBt/FDr4Y6yeuVehaqijVVvtpSmXrGJw==",
|
||||
"dependencies": {
|
||||
"@sentry/core": "7.17.4",
|
||||
"@sentry/types": "7.17.4",
|
||||
"@sentry/utils": "7.17.4",
|
||||
"@sentry/types": "7.19.0",
|
||||
"@sentry/utils": "7.19.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/node/node_modules/@sentry/types": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.19.0.tgz",
|
||||
"integrity": "sha512-oGRAT6lfzoKrxO1mvxiSj0XHxWPd6Gd1wpPGuu6iJo03xgWDS+MIlD1h2unqL4N5fAzLjzmbC2D2lUw50Kn2pA==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/node/node_modules/@sentry/utils": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.19.0.tgz",
|
||||
"integrity": "sha512-2L6lq+c9Ol2uiRxQDdcgoapmHJp24MhMN0gIkn2alSfMJ+ls6bGXzQHx6JAIdoOiwFQXRZHKL9ecfAc8O+vItA==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.19.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/tracing": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.19.0.tgz",
|
||||
"integrity": "sha512-SWY17M3TsgBePaGowUcSqBwaT0TJQzuNexVnLojuU0k6F57L9hubvP9zaoosoCfARXQ/3NypAFWnlJyf570rFQ==",
|
||||
"dependencies": {
|
||||
"@sentry/core": "7.19.0",
|
||||
"@sentry/types": "7.19.0",
|
||||
"@sentry/utils": "7.19.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/tracing/node_modules/@sentry/core": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.19.0.tgz",
|
||||
"integrity": "sha512-YF9cTBcAnO4R44092BJi5Wa2/EO02xn2ziCtmNgAVTN2LD31a/YVGxGBt/FDr4Y6yeuVehaqijVVvtpSmXrGJw==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.19.0",
|
||||
"@sentry/utils": "7.19.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/tracing/node_modules/@sentry/types": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.19.0.tgz",
|
||||
"integrity": "sha512-oGRAT6lfzoKrxO1mvxiSj0XHxWPd6Gd1wpPGuu6iJo03xgWDS+MIlD1h2unqL4N5fAzLjzmbC2D2lUw50Kn2pA==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/tracing/node_modules/@sentry/utils": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.19.0.tgz",
|
||||
"integrity": "sha512-2L6lq+c9Ol2uiRxQDdcgoapmHJp24MhMN0gIkn2alSfMJ+ls6bGXzQHx6JAIdoOiwFQXRZHKL9ecfAc8O+vItA==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.19.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -2915,6 +2983,22 @@
|
||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/swagger-jsdoc": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.1.tgz",
|
||||
"integrity": "sha512-+MUpcbyxD528dECUBCEVm6abNuORdbuGjbrUdHDeAQ+rkPuo2a+L4N02WJHF3bonSSE6SJ3dUJwF2V6+cHnf0w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/swagger-ui-express": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz",
|
||||
"integrity": "sha512-jqCjGU/tGEaqIplPy3WyQg+Nrp6y80DCFnDEAvVKWkJyv0VivSSDCChkppHRHAablvInZe6pijDFMnavtN0vqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/express": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
@ -4499,9 +4583,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.6.0.tgz",
|
||||
"integrity": "sha512-HFN2+4ZGdkQOS8Qli4z6knmJFnw6lZed67o6b7RGplWeb1Z0s8VXaj3dUgPIdm9hrhZXTRpCTHXA0/2Eqex0vA==",
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz",
|
||||
"integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==",
|
||||
"engines": {
|
||||
"node": ">= 12.9.0"
|
||||
},
|
||||
@ -6434,9 +6518,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mongoose": {
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.7.1.tgz",
|
||||
"integrity": "sha512-qbagtqSyvIhUz4EWzXC00EA0DJHFrQwlzTlNGX5DjiESoJiPKqkEga1k9hviFKRFgBna+OlW54mkdi+0+AqxCw==",
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.7.2.tgz",
|
||||
"integrity": "sha512-lrP2V5U1qhaf+z33fiIn7aYAZZ1fVDly+TkFRjTujNBF/FIHESATj2RbgAOSlWqv32fsZXkXejXzeVfjbv35Ow==",
|
||||
"dependencies": {
|
||||
"bson": "^4.7.0",
|
||||
"kareem": "2.4.1",
|
||||
@ -10490,9 +10574,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.8.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
|
||||
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
|
||||
"version": "4.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
|
||||
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@ -13040,28 +13124,80 @@
|
||||
}
|
||||
},
|
||||
"@sentry/node": {
|
||||
"version": "7.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.17.4.tgz",
|
||||
"integrity": "sha512-cR+Gsir9c/tzFWxvk4zXkMQy6tNRHEYixHrb88XIjZVYDqDS9l2/bKs5nJusdmaUeLtmPp5Et2o7RJyS7gvKTQ==",
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.19.0.tgz",
|
||||
"integrity": "sha512-yG7Tx32WqOkEHVotFLrumCcT9qlaSDTkFNZ+yLSvZXx74ifsE781DzBA9W7K7bBdYO3op+p2YdsOKzf3nPpAyQ==",
|
||||
"requires": {
|
||||
"@sentry/core": "7.17.4",
|
||||
"@sentry/types": "7.17.4",
|
||||
"@sentry/utils": "7.17.4",
|
||||
"@sentry/core": "7.19.0",
|
||||
"@sentry/types": "7.19.0",
|
||||
"@sentry/utils": "7.19.0",
|
||||
"cookie": "^0.4.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"lru_map": "^0.3.3",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/core": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.19.0.tgz",
|
||||
"integrity": "sha512-YF9cTBcAnO4R44092BJi5Wa2/EO02xn2ziCtmNgAVTN2LD31a/YVGxGBt/FDr4Y6yeuVehaqijVVvtpSmXrGJw==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.19.0",
|
||||
"@sentry/utils": "7.19.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.19.0.tgz",
|
||||
"integrity": "sha512-oGRAT6lfzoKrxO1mvxiSj0XHxWPd6Gd1wpPGuu6iJo03xgWDS+MIlD1h2unqL4N5fAzLjzmbC2D2lUw50Kn2pA=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.19.0.tgz",
|
||||
"integrity": "sha512-2L6lq+c9Ol2uiRxQDdcgoapmHJp24MhMN0gIkn2alSfMJ+ls6bGXzQHx6JAIdoOiwFQXRZHKL9ecfAc8O+vItA==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.19.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sentry/tracing": {
|
||||
"version": "7.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.17.4.tgz",
|
||||
"integrity": "sha512-9Fz6DI16ddnd970mlB5MiCNRSmSXp4SVZ1Yv3L22oS3kQeNxjBTE+htYNwJzSPrQp9aL/LqTYwlnrCy24u9XQA==",
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.19.0.tgz",
|
||||
"integrity": "sha512-SWY17M3TsgBePaGowUcSqBwaT0TJQzuNexVnLojuU0k6F57L9hubvP9zaoosoCfARXQ/3NypAFWnlJyf570rFQ==",
|
||||
"requires": {
|
||||
"@sentry/core": "7.17.4",
|
||||
"@sentry/types": "7.17.4",
|
||||
"@sentry/utils": "7.17.4",
|
||||
"@sentry/core": "7.19.0",
|
||||
"@sentry/types": "7.19.0",
|
||||
"@sentry/utils": "7.19.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/core": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.19.0.tgz",
|
||||
"integrity": "sha512-YF9cTBcAnO4R44092BJi5Wa2/EO02xn2ziCtmNgAVTN2LD31a/YVGxGBt/FDr4Y6yeuVehaqijVVvtpSmXrGJw==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.19.0",
|
||||
"@sentry/utils": "7.19.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.19.0.tgz",
|
||||
"integrity": "sha512-oGRAT6lfzoKrxO1mvxiSj0XHxWPd6Gd1wpPGuu6iJo03xgWDS+MIlD1h2unqL4N5fAzLjzmbC2D2lUw50Kn2pA=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.19.0.tgz",
|
||||
"integrity": "sha512-2L6lq+c9Ol2uiRxQDdcgoapmHJp24MhMN0gIkn2alSfMJ+ls6bGXzQHx6JAIdoOiwFQXRZHKL9ecfAc8O+vItA==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.19.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
@ -13337,6 +13473,22 @@
|
||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/swagger-jsdoc": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.1.tgz",
|
||||
"integrity": "sha512-+MUpcbyxD528dECUBCEVm6abNuORdbuGjbrUdHDeAQ+rkPuo2a+L4N02WJHF3bonSSE6SJ3dUJwF2V6+cHnf0w==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/swagger-ui-express": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz",
|
||||
"integrity": "sha512-jqCjGU/tGEaqIplPy3WyQg+Nrp6y80DCFnDEAvVKWkJyv0VivSSDCChkppHRHAablvInZe6pijDFMnavtN0vqA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/express": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"@types/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
@ -14489,9 +14641,9 @@
|
||||
}
|
||||
},
|
||||
"express-rate-limit": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.6.0.tgz",
|
||||
"integrity": "sha512-HFN2+4ZGdkQOS8Qli4z6knmJFnw6lZed67o6b7RGplWeb1Z0s8VXaj3dUgPIdm9hrhZXTRpCTHXA0/2Eqex0vA==",
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz",
|
||||
"integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==",
|
||||
"requires": {}
|
||||
},
|
||||
"express-validator": {
|
||||
@ -15930,9 +16082,9 @@
|
||||
}
|
||||
},
|
||||
"mongoose": {
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.7.1.tgz",
|
||||
"integrity": "sha512-qbagtqSyvIhUz4EWzXC00EA0DJHFrQwlzTlNGX5DjiESoJiPKqkEga1k9hviFKRFgBna+OlW54mkdi+0+AqxCw==",
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.7.2.tgz",
|
||||
"integrity": "sha512-lrP2V5U1qhaf+z33fiIn7aYAZZ1fVDly+TkFRjTujNBF/FIHESATj2RbgAOSlWqv32fsZXkXejXzeVfjbv35Ow==",
|
||||
"requires": {
|
||||
"bson": "^4.7.0",
|
||||
"kareem": "2.4.1",
|
||||
@ -18791,9 +18943,9 @@
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.8.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
|
||||
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ=="
|
||||
"version": "4.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
|
||||
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA=="
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.17.4",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@sentry/node": "^7.14.0",
|
||||
"@sentry/tracing": "^7.14.0",
|
||||
"@sentry/tracing": "^7.19.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"axios": "^1.1.3",
|
||||
"bigint-conversion": "^2.2.2",
|
||||
@ -10,13 +10,13 @@
|
||||
"crypto-js": "^4.1.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "^4.18.1",
|
||||
"express-rate-limit": "^6.5.1",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"express-validator": "^6.14.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"helmet": "^5.1.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jsrp": "^0.2.4",
|
||||
"mongoose": "^6.7.1",
|
||||
"mongoose": "^6.7.2",
|
||||
"nodemailer": "^6.8.0",
|
||||
"posthog-node": "^2.1.0",
|
||||
"query-string": "^7.1.1",
|
||||
@ -24,7 +24,7 @@
|
||||
"stripe": "^10.7.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1",
|
||||
"typescript": "^4.8.4"
|
||||
"typescript": "^4.9.3"
|
||||
},
|
||||
"name": "infisical-api",
|
||||
"version": "1.0.0",
|
||||
@ -56,6 +56,8 @@
|
||||
"@types/jsonwebtoken": "^8.5.9",
|
||||
"@types/node": "^18.11.3",
|
||||
"@types/nodemailer": "^6.4.6",
|
||||
"@types/swagger-jsdoc": "^6.0.1",
|
||||
"@types/swagger-ui-express": "^4.1.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
||||
"@typescript-eslint/parser": "^5.40.1",
|
||||
"eslint": "^8.26.0",
|
||||
|
@ -1,5 +1,5 @@
|
||||
const PORT = process.env.PORT || 4000;
|
||||
const EMAIL_TOKEN_LIFETIME = process.env.EMAIL_TOKEN_LIFETIME! || '86400'; // investigate
|
||||
const EMAIL_TOKEN_LIFETIME = process.env.EMAIL_TOKEN_LIFETIME! || '86400';
|
||||
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!;
|
||||
const JWT_AUTH_LIFETIME = process.env.JWT_AUTH_LIFETIME! || '10d';
|
||||
const JWT_AUTH_SECRET = process.env.JWT_AUTH_SECRET!;
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
STRIPE_PRODUCT_CARD_AUTH
|
||||
} from '../config';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
const stripe = new Stripe(STRIPE_SECRET_KEY, {
|
||||
apiVersion: '2022-08-01'
|
||||
});
|
||||
|
@ -66,7 +66,7 @@ const checkEmailVerification = async ({
|
||||
email,
|
||||
token: code
|
||||
});
|
||||
|
||||
|
||||
if (!token) throw new Error('Failed to find email verification token');
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
@ -106,7 +106,7 @@ const initializeDefaultOrg = async ({
|
||||
|
||||
// initialize a default workspace inside the new organization
|
||||
const workspace = await createWorkspace({
|
||||
name: `${user.firstName}'s Project`,
|
||||
name: `Example Project`,
|
||||
organizationId: organization._id.toString()
|
||||
});
|
||||
|
||||
|
@ -4,9 +4,9 @@ import cors from 'cors';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import mongoose from 'mongoose';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
import * as Sentry from '@sentry/node';
|
||||
// import { PostHogClient } from './services';
|
||||
import { PORT, SENTRY_DSN, NODE_ENV, MONGO_URL, SITE_URL, POSTHOG_PROJECT_API_KEY, POSTHOG_HOST, TELEMETRY_ENABLED } from './config';
|
||||
import { apiLimiter } from './helpers/rateLimiter';
|
||||
|
||||
@ -19,12 +19,6 @@ Sentry.init({
|
||||
environment: NODE_ENV
|
||||
});
|
||||
|
||||
// PostHogClient.init({
|
||||
// projectApiKey: POSTHOG_PROJECT_API_KEY,
|
||||
// host: POSTHOG_HOST,
|
||||
// telemetryEnabled: TELEMETRY_ENABLED
|
||||
// });
|
||||
|
||||
import {
|
||||
signup as signupRouter,
|
||||
auth as authRouter,
|
||||
|
@ -18,7 +18,7 @@ const tokenSchema = new Schema<IToken>({
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
expires: EMAIL_TOKEN_LIFETIME,
|
||||
expires: parseInt(EMAIL_TOKEN_LIFETIME),
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
|
140
cli/packages/cmd/export.go
Normal file
140
cli/packages/cmd/export.go
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
FormatDotenv string = "dotenv"
|
||||
FormatJson string = "json"
|
||||
FormatCSV string = "csv"
|
||||
)
|
||||
|
||||
// exportCmd represents the export command
|
||||
var exportCmd = &cobra.Command{
|
||||
Use: "export",
|
||||
Short: "Used to export environment variables to a file",
|
||||
DisableFlagsInUseLine: true,
|
||||
Example: "infisical export --env=prod --format=json > secrets.json",
|
||||
Args: cobra.NoArgs,
|
||||
PreRun: toggleDebug,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
envName, err := cmd.Flags().GetString("env")
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse the environment flag")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
shouldExpandSecrets, err := cmd.Flags().GetBool("expand")
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse the substitute flag")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
projectId, err := cmd.Flags().GetString("projectId")
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse the project id flag")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
format, err := cmd.Flags().GetString("format")
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse the format flag")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
envsFromApi, err := util.GetAllEnvironmentVariables(projectId, envName)
|
||||
if err != nil {
|
||||
log.Errorln("Something went wrong when pulling secrets using your Infisical token. Double check the token, project id or environment name (dev, prod, ect.)")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
var output string
|
||||
if shouldExpandSecrets {
|
||||
substitutions := util.SubstituteSecrets(envsFromApi)
|
||||
output, err = formatEnvs(substitutions, format)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
output, err = formatEnvs(envsFromApi, format)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Print(output)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(exportCmd)
|
||||
exportCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
|
||||
exportCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from")
|
||||
exportCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
||||
exportCmd.Flags().StringP("format", "f", "dotenv", "Set the format of the output file (dotenv, json, csv)")
|
||||
}
|
||||
|
||||
// Format according to the format flag
|
||||
func formatEnvs(envs []models.SingleEnvironmentVariable, format string) (string, error) {
|
||||
switch strings.ToLower(format) {
|
||||
case FormatDotenv:
|
||||
return formatAsDotEnv(envs), nil
|
||||
case FormatJson:
|
||||
return formatAsJson(envs), nil
|
||||
case FormatCSV:
|
||||
return formatAsCSV(envs), nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid format flag: %s", format)
|
||||
}
|
||||
}
|
||||
|
||||
// Format environment variables as a CSV file
|
||||
func formatAsCSV(envs []models.SingleEnvironmentVariable) string {
|
||||
csvString := &strings.Builder{}
|
||||
writer := csv.NewWriter(csvString)
|
||||
writer.Write([]string{"Key", "Value"})
|
||||
for _, env := range envs {
|
||||
writer.Write([]string{env.Key, env.Value})
|
||||
}
|
||||
writer.Flush()
|
||||
return csvString.String()
|
||||
}
|
||||
|
||||
// Format environment variables as a dotenv file
|
||||
func formatAsDotEnv(envs []models.SingleEnvironmentVariable) string {
|
||||
var dotenv string
|
||||
for _, env := range envs {
|
||||
dotenv += fmt.Sprintf("%s='%s'\n", env.Key, env.Value)
|
||||
}
|
||||
return dotenv
|
||||
}
|
||||
|
||||
// Format environment variables as a JSON file
|
||||
func formatAsJson(envs []models.SingleEnvironmentVariable) string {
|
||||
// Dump as a json array
|
||||
json, err := json.Marshal(envs)
|
||||
if err != nil {
|
||||
log.Errorln("Unable to marshal environment variables to JSON")
|
||||
log.Debugln(err)
|
||||
return ""
|
||||
}
|
||||
return string(json)
|
||||
}
|
@ -36,7 +36,7 @@ var initCmd = &cobra.Command{
|
||||
return
|
||||
}
|
||||
|
||||
if util.WorkspaceConfigFileExists() {
|
||||
if util.WorkspaceConfigFileExistsInCurrentPath() {
|
||||
shouldOverride, err := shouldOverrideWorkspacePrompt()
|
||||
if err != nil {
|
||||
log.Errorln("Unable to parse your answer")
|
||||
|
@ -114,7 +114,7 @@ func init() {
|
||||
|
||||
func askForLoginCredentials() (email string, password string, err error) {
|
||||
validateEmail := func(input string) error {
|
||||
matched, err := regexp.MatchString("^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", input)
|
||||
matched, err := regexp.MatchString("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", input)
|
||||
if err != nil || !matched {
|
||||
return errors.New("this doesn't look like an email address")
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{
|
||||
Short: "Infisical CLI is used to inject environment variables into any process",
|
||||
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
|
||||
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
|
||||
Version: "0.1.5",
|
||||
Version: "0.1.10",
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
|
@ -47,53 +47,17 @@ var runCmd = &cobra.Command{
|
||||
return
|
||||
}
|
||||
|
||||
var envsFromApi []models.SingleEnvironmentVariable
|
||||
infisicalToken := os.Getenv(util.INFISICAL_TOKEN_NAME)
|
||||
if infisicalToken == "" {
|
||||
hasUserLoggedInbefore, loggedInUserEmail, err := util.IsUserLoggedIn()
|
||||
if err != nil {
|
||||
log.Info("Unexpected issue occurred while checking login status. To see more details, add flag --debug")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !hasUserLoggedInbefore {
|
||||
log.Infoln("No logged in user. To login, please run command [infisical login]")
|
||||
return
|
||||
}
|
||||
|
||||
userCreds, err := util.GetUserCredsFromKeyRing(loggedInUserEmail)
|
||||
if err != nil {
|
||||
log.Infoln("Unable to get user creds from key ring")
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !util.WorkspaceConfigFileExists() {
|
||||
log.Infoln("Your project is not connected to a project yet. Run command [infisical init]")
|
||||
return
|
||||
}
|
||||
|
||||
envsFromApi, err = util.GetSecretsFromAPIUsingCurrentLoggedInUser(envName, userCreds)
|
||||
if err != nil {
|
||||
log.Errorln("Something went wrong when pulling secrets using your logged in credentials. If the issue persists, double check your project id/try logging in again.")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
envsFromApi, err = util.GetSecretsFromAPIUsingInfisicalToken(infisicalToken, envName, projectId)
|
||||
if err != nil {
|
||||
log.Errorln("Something went wrong when pulling secrets using your Infisical token. Double check the token, project id or environment name (dev, prod, ect.)")
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
secrets, err := util.GetAllEnvironmentVariables(projectId, envName)
|
||||
if err != nil {
|
||||
log.Debugln(err)
|
||||
return
|
||||
}
|
||||
|
||||
if shouldExpandSecrets {
|
||||
substitutions := util.SubstituteSecrets(envsFromApi)
|
||||
execCmd(args[0], args[1:], substitutions)
|
||||
secretsWithSubstitutions := util.SubstituteSecrets(secrets)
|
||||
execCmd(args[0], args[1:], secretsWithSubstitutions)
|
||||
} else {
|
||||
execCmd(args[0], args[1:], envsFromApi)
|
||||
execCmd(args[0], args[1:], secrets)
|
||||
}
|
||||
|
||||
},
|
||||
@ -108,9 +72,12 @@ func init() {
|
||||
|
||||
// Credit: inspired by AWS Valut
|
||||
func execCmd(command string, args []string, envs []models.SingleEnvironmentVariable) error {
|
||||
log.Infof("\x1b[%dm%s\x1b[0m", 32, "\u2713 Injected Infisical secrets into your application process successfully")
|
||||
log.Debugln("Secrets to inject:", envs)
|
||||
numberOfSecretsInjected := fmt.Sprintf("\u2713 Injected %v Infisical secrets into your application process successfully", len(envs))
|
||||
|
||||
log.Infof("\x1b[%dm%s\x1b[0m", 32, numberOfSecretsInjected)
|
||||
log.Debugf("executing command: %s %s \n", command, strings.Join(args, " "))
|
||||
log.Debugln("Secrets injected:", envs)
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
|
@ -5,10 +5,13 @@ import log "github.com/sirupsen/logrus"
|
||||
// Custom error type so that we can give helpful messages in CLI
|
||||
type Error struct {
|
||||
Err error
|
||||
DebugMessage string
|
||||
FriendlyMessage string
|
||||
}
|
||||
|
||||
func (e *Error) printFriendlyMessage() {
|
||||
log.Infoln(e.FriendlyMessage)
|
||||
}
|
||||
|
||||
func (e *Error) printDebuError() {
|
||||
log.Debugln(e.Err)
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func ConfigFileExists() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func WorkspaceConfigFileExists() bool {
|
||||
func WorkspaceConfigFileExistsInCurrentPath() bool {
|
||||
if _, err := os.Stat(INFISICAL_WORKSPACE_CONFIG_FILE_NAME); err == nil {
|
||||
return true
|
||||
} else {
|
||||
@ -90,3 +90,65 @@ func GetFullConfigFilePath() (fullPathToFile string, fullPathToDirectory string,
|
||||
fullDirPath := fmt.Sprintf("%s/%s", homeDir, CONFIG_FOLDER_NAME)
|
||||
return fullPath, fullDirPath, err
|
||||
}
|
||||
|
||||
// Given a path to a workspace config, unmarshal workspace config
|
||||
func GetWorkspaceConfigByPath(path string) (workspaceConfig models.WorkspaceConfigFile, err error) {
|
||||
workspaceConfigFileAsBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return models.WorkspaceConfigFile{}, fmt.Errorf("GetWorkspaceConfigByPath: Unable to read workspace config file because [%s]", err)
|
||||
}
|
||||
|
||||
var workspaceConfigFile models.WorkspaceConfigFile
|
||||
err = json.Unmarshal(workspaceConfigFileAsBytes, &workspaceConfigFile)
|
||||
if err != nil {
|
||||
return models.WorkspaceConfigFile{}, fmt.Errorf("GetWorkspaceConfigByPath: Unable to unmarshal workspace config file because [%s]", err)
|
||||
}
|
||||
|
||||
return workspaceConfigFile, nil
|
||||
}
|
||||
|
||||
// Will get the list of .infisical.json files that are located
|
||||
// within the root of each sub folder from where the CLI is ran from
|
||||
func GetAllWorkSpaceConfigsStartingFromCurrentPath() (workspaces []models.WorkspaceConfigFile, err error) {
|
||||
currentDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetAllProjectConfigs: unable to get the current directory because [%s]", err)
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(currentDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetAllProjectConfigs: unable to read the contents of the current directory because [%s]", err)
|
||||
}
|
||||
|
||||
listOfWorkSpaceConfigs := []models.WorkspaceConfigFile{}
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && file.Name() == INFISICAL_WORKSPACE_CONFIG_FILE_NAME {
|
||||
pathToWorkspaceConfigFile := currentDir + "/" + INFISICAL_WORKSPACE_CONFIG_FILE_NAME
|
||||
|
||||
workspaceConfig, err := GetWorkspaceConfigByPath(pathToWorkspaceConfigFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetAllProjectConfigs: Unable to get config file because [%s]", err)
|
||||
}
|
||||
|
||||
listOfWorkSpaceConfigs = append(listOfWorkSpaceConfigs, workspaceConfig)
|
||||
|
||||
} else if file.IsDir() {
|
||||
pathToSubFolder := currentDir + "/" + file.Name()
|
||||
pathToMaybeWorkspaceConfigFile := pathToSubFolder + "/" + INFISICAL_WORKSPACE_CONFIG_FILE_NAME
|
||||
|
||||
_, err := os.Stat(pathToMaybeWorkspaceConfigFile)
|
||||
if err != nil {
|
||||
continue // workspace config file doesn't exist
|
||||
}
|
||||
|
||||
workspaceConfig, err := GetWorkspaceConfigByPath(pathToMaybeWorkspaceConfigFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetAllProjectConfigs: Unable to get config file because [%s]", err)
|
||||
}
|
||||
|
||||
listOfWorkSpaceConfigs = append(listOfWorkSpaceConfigs, workspaceConfig)
|
||||
}
|
||||
}
|
||||
|
||||
return listOfWorkSpaceConfigs, nil
|
||||
}
|
||||
|
@ -3,12 +3,9 @@ package util
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func DecryptSymmetric(key []byte, encryptedPrivateKey []byte, tag []byte, IV []byte) ([]byte, error) {
|
||||
log.Debugln("Key:", key, "encryptedPrivateKey", encryptedPrivateKey, "tag", tag, "IV", IV)
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@ -13,19 +14,7 @@ import (
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
)
|
||||
|
||||
func GetSecretsFromAPIUsingCurrentLoggedInUser(envName string, userCreds models.UserCredentials) ([]models.SingleEnvironmentVariable, error) {
|
||||
log.Debugln("envName", envName, "userCreds", userCreds)
|
||||
// check if user has configured a workspace
|
||||
workspace, err := GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to read workspace file:", err)
|
||||
}
|
||||
|
||||
// create http client
|
||||
httpClient := resty.New().
|
||||
SetAuthToken(userCreds.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
func getSecretsByWorkspaceIdAndEnvName(httpClient resty.Client, envName string, workspace models.WorkspaceConfigFile, userCreds models.UserCredentials) (listOfSecrets []models.SingleEnvironmentVariable, err error) {
|
||||
var pullSecretsRequestResponse models.PullSecretsResponse
|
||||
response, err := httpClient.
|
||||
R().
|
||||
@ -34,14 +23,11 @@ func GetSecretsFromAPIUsingCurrentLoggedInUser(envName string, userCreds models.
|
||||
SetResult(&pullSecretsRequestResponse).
|
||||
Get(fmt.Sprintf("%v/v1/secret/%v", INFISICAL_URL, workspace.WorkspaceId)) // need to change workspace id
|
||||
|
||||
log.Debugln("Response from get secrets:", response)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode() > 299 {
|
||||
log.Debugln(response)
|
||||
return nil, fmt.Errorf(response.Status())
|
||||
}
|
||||
|
||||
@ -66,7 +52,7 @@ func GetSecretsFromAPIUsingCurrentLoggedInUser(envName string, userCreds models.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugln("workspaceKey", workspaceKey, "nonce", nonce, "senderPublicKey", senderPublicKey, "currentUsersPrivateKey", currentUsersPrivateKey)
|
||||
// log.Debugln("workspaceKey", workspaceKey, "nonce", nonce, "senderPublicKey", senderPublicKey, "currentUsersPrivateKey", currentUsersPrivateKey)
|
||||
workspaceKeyInBytes, _ := box.Open(nil, workspaceKey, (*[24]byte)(nonce), (*[32]byte)(senderPublicKey), (*[32]byte)(currentUsersPrivateKey))
|
||||
var listOfEnv []models.SingleEnvironmentVariable
|
||||
|
||||
@ -100,6 +86,32 @@ func GetSecretsFromAPIUsingCurrentLoggedInUser(envName string, userCreds models.
|
||||
return listOfEnv, nil
|
||||
}
|
||||
|
||||
func GetSecretsFromAPIUsingCurrentLoggedInUser(envName string, userCreds models.UserCredentials) ([]models.SingleEnvironmentVariable, error) {
|
||||
log.Debugln("GetSecretsFromAPIUsingCurrentLoggedInUser", "envName", envName, "userCreds", userCreds)
|
||||
// check if user has configured a workspace
|
||||
workspaces, err := GetAllWorkSpaceConfigsStartingFromCurrentPath()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to read workspace file(s):", err)
|
||||
}
|
||||
|
||||
// create http client
|
||||
httpClient := resty.New().
|
||||
SetAuthToken(userCreds.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
secrets := []models.SingleEnvironmentVariable{}
|
||||
for _, workspace := range workspaces {
|
||||
secretsFromAPI, err := getSecretsByWorkspaceIdAndEnvName(*httpClient, envName, workspace, userCreds)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetSecretsFromAPIUsingCurrentLoggedInUser: Unable to get secrets by workspace id and env name")
|
||||
}
|
||||
|
||||
secrets = append(secrets, secretsFromAPI...)
|
||||
}
|
||||
|
||||
return secrets, nil
|
||||
}
|
||||
|
||||
func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, projectId string) ([]models.SingleEnvironmentVariable, error) {
|
||||
if infisicalToken == "" || projectId == "" || envName == "" {
|
||||
return nil, errors.New("infisical token, project id and or environment name cannot be empty")
|
||||
@ -126,7 +138,6 @@ func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string,
|
||||
}
|
||||
|
||||
if response.StatusCode() > 299 {
|
||||
log.Debugln(response)
|
||||
return nil, fmt.Errorf(response.Status())
|
||||
}
|
||||
|
||||
@ -184,6 +195,60 @@ func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string,
|
||||
return listOfEnv, nil
|
||||
}
|
||||
|
||||
func GetAllEnvironmentVariables(projectId string, envName string) ([]models.SingleEnvironmentVariable, error) {
|
||||
infisicalToken := os.Getenv(INFISICAL_TOKEN_NAME)
|
||||
|
||||
if infisicalToken == "" {
|
||||
hasUserLoggedInbefore, loggedInUserEmail, err := IsUserLoggedIn()
|
||||
if err != nil {
|
||||
log.Info("Unexpected issue occurred while checking login status. To see more details, add flag --debug")
|
||||
log.Debugln(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !hasUserLoggedInbefore {
|
||||
log.Infoln("No logged in user. To login, please run command [infisical login]")
|
||||
return nil, fmt.Errorf("user not logged in")
|
||||
}
|
||||
|
||||
userCreds, err := GetUserCredsFromKeyRing(loggedInUserEmail)
|
||||
if err != nil {
|
||||
log.Infoln("Unable to get user creds from key ring")
|
||||
log.Debug(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workspaceConfigs, err := GetAllWorkSpaceConfigsStartingFromCurrentPath()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to check if you have a %s file in your current directory", INFISICAL_WORKSPACE_CONFIG_FILE_NAME)
|
||||
}
|
||||
|
||||
if len(workspaceConfigs) == 0 {
|
||||
log.Infoln("Your local project is not connected to a Infisical project yet. Run command [infisical init]")
|
||||
return nil, fmt.Errorf("project not initialized")
|
||||
}
|
||||
|
||||
envsFromApi, err := GetSecretsFromAPIUsingCurrentLoggedInUser(envName, userCreds)
|
||||
if err != nil {
|
||||
log.Errorln("Something went wrong when pulling secrets using your logged in credentials. If the issue persists, double check your project id/try logging in again.")
|
||||
log.Debugln(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return envsFromApi, nil
|
||||
|
||||
} else {
|
||||
envsFromApi, err := GetSecretsFromAPIUsingInfisicalToken(infisicalToken, envName, projectId)
|
||||
if err != nil {
|
||||
log.Errorln("Something went wrong when pulling secrets using your Infisical token. Double check the token, project id or environment name (dev, prod, ect.)")
|
||||
log.Debugln(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return envsFromApi, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetWorkSpacesFromAPI(userCreds models.UserCredentials) (workspaces []models.Workspace, err error) {
|
||||
// create http client
|
||||
httpClient := resty.New().
|
||||
|
@ -23,7 +23,6 @@ services:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
image: infisical/backend
|
||||
volumes:
|
||||
- ./backend/src:/app/src
|
||||
- ./backend/nodemon.json:/app/nodemon.json
|
||||
@ -43,7 +42,6 @@ services:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile.dev
|
||||
image: infisical/frontend
|
||||
volumes:
|
||||
- ./frontend/pages:/app/pages
|
||||
- ./frontend/public:/app/public
|
||||
@ -52,10 +50,8 @@ services:
|
||||
env_file: .env
|
||||
environment:
|
||||
- NEXT_PUBLIC_ENV=development
|
||||
- NEXT_PUBLIC_WEBSITE_URL=${SITE_URL}
|
||||
- NEXT_PUBLIC_POSTHOG_HOST=${POSTHOG_HOST}
|
||||
- NEXT_PUBLIC_POSTHOG_API_KEY=${POSTHOG_PROJECT_API_KEY}
|
||||
- NEXT_PUBLIC_TELEMETRY_ENABLED=${TELEMETRY_ENABLED}
|
||||
- NEXT_PUBLIC_STRIPE_PRODUCT_PRO=${STRIPE_PRODUCT_PRO}
|
||||
- NEXT_PUBLIC_STRIPE_PRODUCT_STARTER=${STRIPE_PRODUCT_STARTER}
|
||||
networks:
|
||||
- infisical-dev
|
||||
|
||||
@ -76,6 +72,8 @@ services:
|
||||
container_name: infisical-dev-mongo-express
|
||||
image: mongo-express
|
||||
restart: always
|
||||
depends_on:
|
||||
- mongo
|
||||
env_file: .env
|
||||
environment:
|
||||
- ME_CONFIG_MONGODB_ADMINUSERNAME=${MONGO_USERNAME}
|
||||
@ -91,4 +89,4 @@ volumes:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
infisical-dev:
|
||||
infisical-dev:
|
||||
|
@ -17,14 +17,10 @@ services:
|
||||
- infisical
|
||||
|
||||
backend:
|
||||
platform: linux/amd64
|
||||
container_name: infisical-backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- mongo
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
image: infisical/backend
|
||||
command: npm run start
|
||||
env_file: .env
|
||||
@ -34,21 +30,17 @@ services:
|
||||
- infisical
|
||||
|
||||
frontend:
|
||||
platform: linux/amd64
|
||||
container_name: infisical-frontend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- backend
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile.prod
|
||||
image: infisical/frontend
|
||||
env_file: .env
|
||||
environment:
|
||||
- NEXT_PUBLIC_ENV=production
|
||||
- NEXT_PUBLIC_WEBSITE_URL=${SITE_URL}
|
||||
- NEXT_PUBLIC_POSTHOG_HOST=${POSTHOG_HOST}
|
||||
- NEXT_PUBLIC_POSTHOG_API_KEY=${POSTHOG_PROJECT_API_KEY}
|
||||
# - NEXT_PUBLIC_POSTHOG_API_KEY=${POSTHOG_PROJECT_API_KEY}
|
||||
- INFISICAL_TELEMETRY_ENABLED=${TELEMETRY_ENABLED}
|
||||
- NEXT_PUBLIC_STRIPE_PRODUCT_PRO=${STRIPE_PRODUCT_PRO}
|
||||
- NEXT_PUBLIC_STRIPE_PRODUCT_STARTER=${STRIPE_PRODUCT_STARTER}
|
||||
networks:
|
||||
- infisical
|
||||
|
||||
@ -70,4 +62,4 @@ volumes:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
infisical:
|
||||
infisical:
|
||||
|
33
docs/cli/commands/export.mdx
Normal file
33
docs/cli/commands/export.mdx
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: "infisical export"
|
||||
---
|
||||
|
||||
```bash
|
||||
infisical export [options]
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
Export environment variables from the platform into a file format.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description | Default value |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` |
|
||||
| `--projectId` | Only required if injecting via the [service token method](../token). If you are not using service token, the project id will be automatically retrieved from the `.infisical.json` located at the root of your local project. | `None` |
|
||||
| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` |
|
||||
| `--format` | Format of the output file. Accepted values: `dotenv`, `csv` and `json` | `dotenv` |
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Export variables to a .env file
|
||||
infisical export > .env
|
||||
|
||||
# Export variables to a CSV file
|
||||
infisical export --format=csv > secrets.csv
|
||||
|
||||
# Export variables to a JSON file
|
||||
infisical export --format=json > secrets.json
|
||||
```
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "Overview"
|
||||
title: "Install"
|
||||
---
|
||||
|
||||
Prerequisite: Set up an account with [Infisical Cloud](https://app.infisical.com) or via a [self-hosted installation](/self-hosting/overview).
|
||||
@ -95,3 +95,9 @@ The Infisical CLI provides a way to inject environment variables from the platfo
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Log in to the Infisical CLI
|
||||
|
||||
```bash
|
||||
infisical login
|
||||
```
|
||||
|
@ -12,8 +12,8 @@ The CLI looks out for an environment variable called the `INFISICAL_TOKEN` which
|
||||
|
||||
A common use-case is to use the Infisical Token to fetch environment variables with Docker. More specifically, a token can be passed to a container as an environment variable for the CLI to authenticate and pull its corresponding secrets. Check out the integration guides for that:
|
||||
|
||||
- [Docker](../../integrations/docker)
|
||||
- [Docker Compose](../../integrations/docker-compose)
|
||||
- [Docker](../../integrations/platforms/docker)
|
||||
- [Docker Compose](../../integrations/platforms/docker-compose)
|
||||
|
||||
<Info>
|
||||
Once the token is expired, the CLI using it will no longer be able to make
|
||||
|
@ -4,12 +4,6 @@ title: "Usage"
|
||||
|
||||
Prerequisite: [Install the CLI](/cli/overview)
|
||||
|
||||
## Log in to the Infisical CLI
|
||||
|
||||
```bash
|
||||
infisical login
|
||||
```
|
||||
|
||||
## Initialize Infisical for your project
|
||||
|
||||
```bash
|
||||
@ -29,11 +23,11 @@ infisical run -- [your application start command]
|
||||
|
||||
Options you can specify:
|
||||
|
||||
| Option | Description | Default value |
|
||||
| ------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` |
|
||||
| `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | `None` |
|
||||
| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` |
|
||||
| Option | Description | Default value |
|
||||
| ------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` |
|
||||
| `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | `None` |
|
||||
| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` |
|
||||
|
||||
## Examples:
|
||||
|
||||
|
16
docs/contributing/FAQ.mdx
Normal file
16
docs/contributing/FAQ.mdx
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "Frequently Asked Questions"
|
||||
description: "Have any questions? [Join our Slack community](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g)."
|
||||
---
|
||||
|
||||
## Problem with SMTP
|
||||
|
||||
You can normally populate `SMTP_USERNAME` and `SMTP_PASSWORD` with your usual login and password (you could also create a 'burner' email). Sometimes, there still are problems.
|
||||
|
||||
You can go to your Gmail account settings > security and enable “less secure apps”. This would allow Infisical to use your Gmail to send emails.
|
||||
|
||||
If it still doesn't work, [this](https://stackoverflow.com/questions/72547853/unable-to-send-email-in-c-sharp-less-secure-app-access-not-longer-available/72553362#72553362) should help.
|
||||
|
||||
## `MONGO_URL` issues
|
||||
|
||||
Your `MONGO_URL` should be something like `mongodb://root:example@mongo:27017/?authSource=admin`. If you want to change it (not recommended), you should make sure that you keep this URL in line with `MONGO_USERNAME=root` and `MONGO_PASSWORD=example`.
|
@ -16,7 +16,7 @@ cd infisical
|
||||
|
||||
## Set up environment variables
|
||||
|
||||
Tweak the `.env` according to your preferences. Refer to the available [environment variables](envars).
|
||||
Tweak the `.env` according to your preferences. Refer to the available [environment variables](/self-hosting/configuration/envars).
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
|
@ -8,24 +8,7 @@ If you're using a self-hosted installation, follow the [setup](/self-hosting/ove
|
||||
|
||||
## Infisical Cloud
|
||||
|
||||
**Step 1:** Open [infisical.com](https://infisical.com/) and click on either "Try Infisical for free" or "Start for free" to head to the signup sequence.
|
||||
|
||||

|
||||
|
||||
**Step 2:** Fill out the signup sequence.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
You'll be prompted to fill out some required fields to set up your account.
|
||||
|
||||
| Field | Description |
|
||||
| ---------- | --------------------------- |
|
||||
| Email | Enter a valid email address |
|
||||
| First name | Your first name |
|
||||
| Last name | Your last name |
|
||||
| Password | Password |
|
||||
Open [infisical.com](https://infisical.com/) and click on either "Try Infisical for free" or "Start for free" to complete the signup sequence.
|
||||
|
||||
Once you've done that, you'll be taken to the dashboard where we've populated some default environment variables for demonstration.
|
||||
|
||||
|
@ -2,12 +2,13 @@
|
||||
title: "Integrations"
|
||||
---
|
||||
|
||||
We’re still early with integrations but you’ll be able to sync environment variables across your entire infrastructure from local development to CI/CD and production.
|
||||
Integrations allow environment variables to be synced across your entire infrastructure from local development to CI/CD and production.
|
||||
|
||||
We're still early with integrations, but expect more soon.
|
||||
|
||||
<Card title="View integrations documentation" icon="link" href="/integrations/overview">
|
||||
View all available integrations and their guide
|
||||
</Card>
|
||||
|
||||

|
||||
|
||||
Check out integrations:
|
||||
|
||||
- [Heroku](/integrations/heroku)
|
||||
- [Docker](/integrations/docker)
|
||||
- [Docker Compose](/integrations/docker-compose)
|
||||
|
@ -2,15 +2,12 @@
|
||||
title: "Organization"
|
||||
---
|
||||
|
||||
By default, Infisical creates an organization under your name such as "John's Organization." To make changes and add members to your organization, head to your organization settings.
|
||||
An organization houses projects and members.
|
||||
|
||||

|
||||
By default, Infisical creates an organization under your name. You can manage your organization in your organization settings.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Feel free to change the name of your organization.
|
||||

|
||||
|
||||
## Members
|
||||
|
||||
@ -19,9 +16,12 @@ Members of an organization can create and add other members to projects within t
|
||||
To add a member to your organization, scroll down to the "Organization Members" section and invite the member via email. They'll receive an email to confirm their organization invitation. If the member is an existing user on the platform, they will be automatically added to the organization.
|
||||
|
||||

|
||||

|
||||
|
||||
Note that access to projects must be provisioned to new members after they've accepted their invitation, and they will not be added to any projects by default.
|
||||
<Note>
|
||||
Note that access to projects must be provisioned to new members after they've
|
||||
accepted their organization invitation, and they will not be added to any
|
||||
projects by default.
|
||||
</Note>
|
||||
|
||||
## Incident contacts
|
||||
|
||||
@ -30,4 +30,3 @@ Incident contacts of an organization are alerted if anything abnormal is detecte
|
||||
To add an incident contact to your organization, scroll down to the "Incident Contacts" section and add their email.
|
||||
|
||||

|
||||

|
||||
|
@ -2,7 +2,7 @@
|
||||
title: "Project"
|
||||
---
|
||||
|
||||
A project houses environment variables for an application or service.
|
||||
A project houses environment variables for an application.
|
||||
|
||||
## Dashboard
|
||||
|
||||
@ -33,20 +33,14 @@ Every environment variable is classified as either personal or shared.
|
||||
|
||||
You can toggle the classification of an environment variable by pressing on its settings:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Search
|
||||
|
||||
You can search for any environment variable by its key.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Sort
|
||||
|
||||
You can sort environment variables alphabetically by their keys.
|
||||
@ -59,8 +53,6 @@ You can hide or un-hide the values of your environment variables. By default, th
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Download as .env
|
||||
|
||||
You can download your environment variables back in a .env file.
|
||||
|
@ -2,20 +2,18 @@
|
||||
title: "Infisical Token"
|
||||
---
|
||||
|
||||
An Infisical Token is needed to authenticate the CLI when there isn't an easy way to manually type in your login credentials to sync environment variables to your applications.
|
||||
An Infisical Token is needed to authenticate the CLI when there isn't an easy way to input your login credentials.
|
||||
|
||||
It grants read-only access to a particular environment and project for a specified amount of time.
|
||||
It's useful for your CI/CD environments and integrations such as [Docker](/integrations/platforms/docker) and [Docker Compose](/integrations/platforms/docker-compose).
|
||||
|
||||
This is useful in the following contexts:
|
||||
To generate the the token, head over to your project settings as shown below.
|
||||
|
||||
- [Docker](/integrations/docker)/[Docker-Compose](/integrations/docker-compose) integration: An Infisical Token can be passed to a Docker container as an environment variable for the CLI to authenticate and pull its corresponding secrets.
|
||||
|
||||
## Generate an Infisical Token
|
||||
|
||||
It's possible to generate an Infisical token in the settings of a project.
|
||||
|
||||

|
||||
|
||||

|
||||
<Note>
|
||||
The token grants read-only access to a particular environment and project for
|
||||
a specified amount of time. Once the token is expired, the CLI using it will no longer be able to make
|
||||
requests with it.
|
||||
</Note>
|
||||
|
||||

|
||||
|
@ -27,7 +27,7 @@ Start syncing environment variables with [Infisical Cloud](https://app.infisical
|
||||
Learn how to configure and deploy Infisical.
|
||||
</Card>
|
||||
<Card
|
||||
href="/integrations/heroku"
|
||||
href="/integrations/overview"
|
||||
title="Integrations"
|
||||
icon="plug"
|
||||
color="#dc2626"
|
||||
|
Binary file not shown.
Before ![]() (image error) Size: 445 KiB After ![]() (image error) Size: 213 KiB ![]() ![]() |
26
docs/integrations/cloud/heroku.mdx
Normal file
26
docs/integrations/cloud/heroku.mdx
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: "Heroku"
|
||||
description: "With this integration, you can automatically sync your secrets to Heroku as soon as you update secrets in Infisical."
|
||||
---
|
||||
|
||||
## Instructions
|
||||
|
||||
### Step 1: Open the integrations console
|
||||
|
||||
Open the Infisical Dashboard. Choose the project in which you want to set up the intergation. Go to the integrations tab in the left sidebar.
|
||||
|
||||
### Step 2: Authenticate with Heroku
|
||||
|
||||
Click on "Heroku" tile. Log in if required and provide the necessary permissions to Infisical. You will afterwards be redirected back to the integrations page.
|
||||
|
||||
Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. With any questions, reach out support@infisical.com.
|
||||
|
||||
### Step 3: Start integration
|
||||
|
||||
Choose a Heroku App that you want to sync the secrets to, and the Infisical project environment that you want to sync the secrets from. Start the integration.
|
||||
|
||||
The integration should now show status 'In Sync'. Every time you edit secrets, they will be automatically pushed to Heroku.
|
||||
|
||||
<Info>
|
||||
If you need to update your integration, you will have to delete the current one and create a new one.
|
||||
</Info>
|
27
docs/integrations/frameworks/django.mdx
Normal file
27
docs/integrations/frameworks/django.mdx
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Django"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Django](https://www.djangoproject.com) project
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize Infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- python manage.py runserver
|
||||
```
|
33
docs/integrations/frameworks/express.mdx
Normal file
33
docs/integrations/frameworks/express.mdx
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: "Express, Fastify, Koa"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
The steps apply to the following non-exhaustive list of frameworks:
|
||||
|
||||
- [Express](https://expressjs.com)
|
||||
- [Fastify](https://www.fastify.io)
|
||||
- [Koa](https://koajs.com)
|
||||
|
||||
## Initialize Infisical for your app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize Infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- npm run dev
|
||||
```
|
27
docs/integrations/frameworks/fiber.mdx
Normal file
27
docs/integrations/frameworks/fiber.mdx
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Fiber"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Fiber](https://gofiber.io/) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize Infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- go run server.go
|
||||
```
|
27
docs/integrations/frameworks/flask.mdx
Normal file
27
docs/integrations/frameworks/flask.mdx
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Flask"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Flask](https://flask.palletsprojects.com/en/2.2.x) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize Infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- flask run
|
||||
```
|
33
docs/integrations/frameworks/gatsby.mdx
Normal file
33
docs/integrations/frameworks/gatsby.mdx
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: "Gatsby"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Gatsby](https://www.gatsbyjs.com) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize Infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- npm run develop
|
||||
```
|
||||
|
||||
<Note>
|
||||
Note that for environment variables to be exposed to the client, you'll have
|
||||
to prefix them with `GATSBY_`. Read more about that
|
||||
[here](https://www.gatsbyjs.com/docs/how-to/local-development/environment-variables/#accessing-environment-variables-in-the-browser).
|
||||
</Note>
|
27
docs/integrations/frameworks/laravel.mdx
Normal file
27
docs/integrations/frameworks/laravel.mdx
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Laravel"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Laravel](https://laravel.com/) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize Infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- php artisan serve
|
||||
```
|
27
docs/integrations/frameworks/nestjs.mdx
Normal file
27
docs/integrations/frameworks/nestjs.mdx
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "NestJS"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [NestJS](https://nestjs.com) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- npm run start:dev
|
||||
```
|
33
docs/integrations/frameworks/nextjs.mdx
Normal file
33
docs/integrations/frameworks/nextjs.mdx
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: "Next.js"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Next.js](https://nextjs.org) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- npm run dev
|
||||
```
|
||||
|
||||
<Note>
|
||||
Note that for environment variables to be exposed to the client, you'll have
|
||||
to prefix them with `NEXT_PUBLIC_`. Read more about that
|
||||
[here](https://nextjs.org/docs/basic-features/environment-variables).
|
||||
</Note>
|
27
docs/integrations/frameworks/nuxt.mdx
Normal file
27
docs/integrations/frameworks/nuxt.mdx
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Nuxt"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Nuxt](https://nuxtjs.org) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- npm run dev
|
||||
```
|
27
docs/integrations/frameworks/rails.mdx
Normal file
27
docs/integrations/frameworks/rails.mdx
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Ruby on Rails"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Rails](https://rubyonrails.org) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize Infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- bin/rails server
|
||||
```
|
27
docs/integrations/frameworks/react.mdx
Normal file
27
docs/integrations/frameworks/react.mdx
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "React"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Create React App](https://create-react-app.dev)
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- npm run dev
|
||||
```
|
27
docs/integrations/frameworks/remix.mdx
Normal file
27
docs/integrations/frameworks/remix.mdx
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Remix"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Remix](https://remix.run) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize Infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- npm run dev
|
||||
```
|
34
docs/integrations/frameworks/vite.mdx
Normal file
34
docs/integrations/frameworks/vite.mdx
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "Vite"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Vite](https://vitejs.dev) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize Infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- npm run dev
|
||||
```
|
||||
|
||||
<Note>
|
||||
Note that for environment variables to be exposed to the client, you'll have
|
||||
to prefix them with `VITE_` and export them from the `vite.config.js` file.
|
||||
Read more about that [here](https://vitejs.dev/guide/env-and-mode.html) and
|
||||
[here](https://main.vitejs.dev/config).
|
||||
</Note>
|
33
docs/integrations/frameworks/vue.mdx
Normal file
33
docs/integrations/frameworks/vue.mdx
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: "Vue"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Vue](https://vuejs.org) app
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- npm run dev
|
||||
```
|
||||
|
||||
<Note>
|
||||
Note that for environment variables to be exposed to the client, you'll have
|
||||
to prefix them with `VUE_APP` Read more about that
|
||||
[here](https://cli.vuejs.org/guide/mode-and-env.html).
|
||||
</Note>
|
@ -1,24 +0,0 @@
|
||||
---
|
||||
title: "Heroku"
|
||||
description: "With this integration, you can automatically sync your secrets to Heroku as soon as you update secrets in Infisical."
|
||||
---
|
||||
|
||||
## Instructions
|
||||
|
||||
### Step 1: Open the integrations csonsole
|
||||
|
||||
Open the Infisical Dashboard. Choose the project in which you want to set up the intergation. Go to the integrations tab in the left sidebar.
|
||||
|
||||
### Step 2: Authenticate with Heroku
|
||||
|
||||
Click on Heroku in the list of available integrations. Log in if asked by Heroku and provide the necessary permissions to Infisical. You will afterwards be redirected back to the integrations page.
|
||||
|
||||
Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. The core Infisical service will always stay end-to-end encrypted. With any questions, reach out support@infisical.com.
|
||||
|
||||
### Step 3: Start integration
|
||||
|
||||
Once the integration is set up, choose a Heroku App that you want to sync the secrets to, and the Infisical project environment that you would to sync the secrets from. Click on the "Start Integration" button.
|
||||
|
||||
### Step 4: You're good to go!
|
||||
|
||||
The integration should now show status 'In Sync'. Every time you edit the secrets, they will be automatically pushed to Heroku. If you want to update anything in your integration, you will have to delete the current one and create a new one.
|
@ -2,23 +2,37 @@
|
||||
title: "Overview"
|
||||
---
|
||||
|
||||
Integrations allow environment variables to be synced across your entire infrastructure from local development to CI/CD and production.
|
||||
Integrations allow environment variables to be synced from Infisical into your local development workflow, CI/CD pipelines, and production infrastructure.
|
||||
|
||||
Missing an integration? Throw in a [request](https://github.com/Infisical/infisical/issues).
|
||||
|
||||
| Integration | Status |
|
||||
| ---------------------------------------------- | ----------- |
|
||||
| [Docker](/integrations/docker) | Available |
|
||||
| [Docker-Compose](/integrations/docker-compose) | Available |
|
||||
| [Heroku](/integrations/heroku) | Available |
|
||||
| Kubernetes | Coming soon |
|
||||
| Vercel | Coming soon |
|
||||
| AWS | Coming soon |
|
||||
| GCP | Coming soon |
|
||||
| Azure | Coming soon |
|
||||
| DigitalOcean | Coming soon |
|
||||
| GitLab | Coming soon |
|
||||
| CircleCI | Coming soon |
|
||||
| TravisCI | Coming soon |
|
||||
| GitHub Actions | Coming soon |
|
||||
| Jenkins | Coming soon |
|
||||
| Integration | Type | Status |
|
||||
| -------------------------------------------------------- | --------- | ----------- |
|
||||
| [Docker](/integrations/platforms/docker) | Platform | Available |
|
||||
| [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available |
|
||||
| Kubernetes | Platform | Coming soon |
|
||||
| [Heroku](/integrations/cloud/heroku) | Cloud | Available |
|
||||
| Vercel | Cloud | Coming soon |
|
||||
| AWS | Cloud | Coming soon |
|
||||
| GCP | Cloud | Coming soon |
|
||||
| Azure | Cloud | Coming soon |
|
||||
| DigitalOcean | Cloud | Coming soon |
|
||||
| GitLab | CI/CD | Coming soon |
|
||||
| CircleCI | CI/CD | Coming soon |
|
||||
| TravisCI | CI/CD | Coming soon |
|
||||
| GitHub Actions | CI/CD | Coming soon |
|
||||
| Jenkins | CI/CD | Coming soon |
|
||||
| [React](/integrations/frameworks/react) | Framework | Available |
|
||||
| [Vue](/integrations/frameworks/vue) | Framework | Available |
|
||||
| [Express](/integrations/frameworks/express) | Framework | Available |
|
||||
| [Next.js](/integrations/frameworks/nextjs) | Framework | Available |
|
||||
| [NestJS](/integrations/frameworks/nestjs) | Framework | Available |
|
||||
| [Nuxt](/integrations/frameworks/nuxt) | Framework | Available |
|
||||
| [Gatsby](/integrations/frameworks/gatsby) | Framework | Available |
|
||||
| [Remix](/integrations/frameworks/remix) | Framework | Available |
|
||||
| [Vite](/integrations/frameworks/vite) | Framework | Available |
|
||||
| [Fiber](/integrations/frameworks/fiber) | Framework | Coming soon |
|
||||
| [Django](/integrations/frameworks/django) | Framework | Available |
|
||||
| [Flask](/integrations/frameworks/flask) | Framework | Available |
|
||||
| [Laravel](/integrations/frameworks/laravel) | Framework | Coming soon |
|
||||
| [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available |
|
||||
|
@ -2,21 +2,23 @@
|
||||
title: "Docker Compose"
|
||||
---
|
||||
|
||||
### Step 1: Add CLI to your Dockerfile
|
||||
The Docker Compose integration enables you to inject environment variables from Infisical into the containers defined in your compose file.
|
||||
|
||||
Follow steps 1 through 3 on our [guide to configure Infisical CLI](../getting-started/cli/installation) in your Dockerfile.
|
||||
## Add the CLI to your Dockerfile(s) start command
|
||||
|
||||
### Step 2: Generate Infisical Token
|
||||
Follow the [guide to configure Infisical CLI](./docker) in your your Dockerfile first.
|
||||
|
||||
## Generate Infisical Token
|
||||
|
||||
In order for Infisical CLI to authenticate and retrieve your project's secrets without exposing your login credentials, you must generate a Infisical Token.
|
||||
To learn how, visit [Infisical Token](../getting-started/cli/infisical-token). Once you have generated the token, keep it handy.
|
||||
To learn how, visit [Infisical Token](../../getting-started/dashboard/token). Once you have generated the token, keep it handy.
|
||||
|
||||
<Info>
|
||||
If you have multiple services and they do not use the same secrets, you will
|
||||
have to generate a Infisical Token for each service.
|
||||
</Info>
|
||||
|
||||
### Step 3: Tell Docker Compose your Infisical Token
|
||||
## Tell Docker Compose your Infisical Token
|
||||
|
||||
For each service you want to inject secrets into, set an environment variable called `INFISICAL_TOKEN` equal to a helpful identifier variable.
|
||||
This will ensure that you can set Infisical Tokens for multiple services.
|
||||
@ -41,7 +43,7 @@ services:
|
||||
- INFISICAL_TOKEN=${INFISICAL_TOKEN_FOR_API}
|
||||
```
|
||||
|
||||
### 4: Export shell variables
|
||||
## Export shell variables
|
||||
|
||||
Next, set the shell variables you defined in your compose file. This can be done manually or via your CI/CD environment. Once done, it will be used to populate the corresponding `INFISICAL_TOKEN`
|
||||
in your Docker Compose file.
|
@ -2,7 +2,9 @@
|
||||
title: "Docker"
|
||||
---
|
||||
|
||||
## Step 1: Add CLI to your Dockerfile
|
||||
Infisical can be used in a Dockerfile to inject environment variables into a Docker container.
|
||||
|
||||
## Add the CLI to your Dockerfile
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Alpine">
|
||||
@ -29,14 +31,10 @@ title: "Docker"
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Step 2: Generate Infisical Token
|
||||
|
||||
[Generate an Infisical Token](../../getting-started/dashboard/token) and keep it handy.
|
||||
|
||||
## Step 3: Set start command of your container
|
||||
## Modify the start command in your Dockerfile
|
||||
|
||||
```dockerfile
|
||||
CMD ["infisical", "--env=[your-project-env-name]", "projectId=[your-project-id]", "run", "---", "<your application start command>"]
|
||||
CMD ["infisical", "--env=[env]", "projectId=[projectId]", "run", "---", "[your application start command]"]
|
||||
|
||||
# example
|
||||
CMD ["infisical", "--env=prod", "projectId=62faf98ae0b05e83239b5da41", "run", "---", "npm run start"]
|
||||
@ -49,12 +47,14 @@ Required options:
|
||||
| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` |
|
||||
| `--projectId` | Used to link a local project to the platform | `None` |
|
||||
|
||||
## Step 4: Feed Docker your Infisical Token
|
||||
## Generate an Infisical Token
|
||||
|
||||
The CLI looks out for an environment variable called the `INFISICAL_TOKEN` which you can set depending on where you run the CLI. If `INFISICAL_TOKEN` is detected by the CLI, it will authenticate and retrieve the environment variables which the token is authorized for.
|
||||
[Generate an Infisical Token](../../getting-started/dashboard/token) and keep it handy.
|
||||
|
||||
## Feed Docker your Infisical Token
|
||||
|
||||
The CLI looks out for an environment variable called `INFISICAL_TOKEN`. If the token is detected, the CLI will authenticate, retrieve, and inject the environment variables which the token is authorized for.
|
||||
|
||||
```bash
|
||||
docker run --env INFISICAL_TOKEN=[the-token-you-got-from-step-2]...
|
||||
docker run --env INFISICAL_TOKEN=[token]...
|
||||
```
|
||||
|
||||
Note: `INFISICAL_TOKEN` is the token you generated in step 2.
|
197
docs/mint.json
197
docs/mint.json
@ -27,90 +27,147 @@
|
||||
"url": "https://app.infisical.com/signup"
|
||||
},
|
||||
"anchors": [
|
||||
{
|
||||
"name": "Security",
|
||||
"icon": "shield-halved",
|
||||
"url": "security"
|
||||
},
|
||||
{
|
||||
"name": "Self-hosting",
|
||||
"icon": "server",
|
||||
"url": "self-hosting"
|
||||
},
|
||||
{
|
||||
"name": "Integrations",
|
||||
"icon": "plug",
|
||||
"url": "integrations"
|
||||
},
|
||||
{
|
||||
"name": "Contributing",
|
||||
"icon": "code",
|
||||
"url": "contributing"
|
||||
},
|
||||
{
|
||||
"name": "Blog",
|
||||
"icon": "newspaper",
|
||||
"url": "https://blog.infisical.com/"
|
||||
},
|
||||
{
|
||||
"name": "Slack",
|
||||
"icon": "slack",
|
||||
"url": "https://join.slack.com/t/infisical-users/shared_invite/zt-1kovn1q6p-p5fvJo7o083naouDkOzgZQ"
|
||||
},
|
||||
{
|
||||
"name": "GitHub",
|
||||
"icon": "github",
|
||||
"url": "https://github.com/Infisical/infisical"
|
||||
}
|
||||
],
|
||||
"navigation": [
|
||||
{
|
||||
"group": " ",
|
||||
"group": "Overview",
|
||||
"pages": [
|
||||
"getting-started/introduction",
|
||||
"getting-started/quickstart",
|
||||
"getting-started/features"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Platform",
|
||||
"pages": [
|
||||
"getting-started/dashboard/organization",
|
||||
"getting-started/dashboard/project",
|
||||
"getting-started/dashboard/integrations",
|
||||
"getting-started/dashboard/token"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "CLI",
|
||||
"pages": [
|
||||
"cli/overview",
|
||||
"cli/usage",
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Commands",
|
||||
"pages": [
|
||||
"getting-started/introduction",
|
||||
"getting-started/quickstart",
|
||||
"getting-started/features"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Security",
|
||||
"pages": [
|
||||
"security/overview",
|
||||
"security/data-model",
|
||||
"security/mechanics"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Web UI",
|
||||
"pages": [
|
||||
"getting-started/dashboard/create-account",
|
||||
"getting-started/dashboard/organization",
|
||||
"getting-started/dashboard/project",
|
||||
"getting-started/dashboard/integrations",
|
||||
"getting-started/dashboard/token"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "CLI",
|
||||
"pages": [
|
||||
"cli/overview",
|
||||
"cli/usage",
|
||||
{
|
||||
"group": "Reference",
|
||||
"pages": [
|
||||
"cli/reference/commands",
|
||||
"cli/reference/login",
|
||||
"cli/reference/init",
|
||||
"cli/reference/run"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"pages": [
|
||||
"integrations/overview",
|
||||
"integrations/heroku",
|
||||
"integrations/docker",
|
||||
"integrations/docker-compose"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Self-hosting",
|
||||
"pages": [
|
||||
"self-hosting/overview",
|
||||
{
|
||||
"group": "Deployments",
|
||||
"pages": ["self-hosting/deployments/linux"]
|
||||
},
|
||||
{
|
||||
"group": "Configuration",
|
||||
"pages": ["self-hosting/configuration/envars"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Contributing",
|
||||
"pages": [
|
||||
"contributing/overview",
|
||||
"contributing/code-of-conduct",
|
||||
"contributing/developing"
|
||||
"cli/commands/login",
|
||||
"cli/commands/init",
|
||||
"cli/commands/run",
|
||||
"cli/commands/export"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Self-hosting",
|
||||
"pages": [
|
||||
"self-hosting/overview",
|
||||
{
|
||||
"group": "Deployments options",
|
||||
"pages": [
|
||||
"self-hosting/deployments/linux",
|
||||
"self-hosting/deployments/kubernetes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Configuration",
|
||||
"pages": ["self-hosting/configuration/envars"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"pages": [
|
||||
"integrations/overview"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Platforms",
|
||||
"pages": [
|
||||
"integrations/platforms/docker",
|
||||
"integrations/platforms/docker-compose"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Cloud",
|
||||
"pages": [
|
||||
"integrations/cloud/heroku"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Frameworks",
|
||||
"pages": [
|
||||
"integrations/frameworks/react",
|
||||
"integrations/frameworks/vue",
|
||||
"integrations/frameworks/express",
|
||||
"integrations/frameworks/nextjs",
|
||||
"integrations/frameworks/nestjs",
|
||||
"integrations/frameworks/nuxt",
|
||||
"integrations/frameworks/gatsby",
|
||||
"integrations/frameworks/remix",
|
||||
"integrations/frameworks/vite",
|
||||
"integrations/frameworks/fiber",
|
||||
"integrations/frameworks/django",
|
||||
"integrations/frameworks/flask",
|
||||
"integrations/frameworks/laravel",
|
||||
"integrations/frameworks/rails"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Security",
|
||||
"pages": [
|
||||
"security/overview",
|
||||
"security/data-model",
|
||||
"security/mechanics"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Contributing",
|
||||
"pages": [
|
||||
"contributing/overview",
|
||||
"contributing/code-of-conduct",
|
||||
"contributing/developing",
|
||||
"contributing/FAQ"
|
||||
]
|
||||
}
|
||||
],
|
||||
"backgroundImage": "/images/background.png"
|
||||
|
@ -9,24 +9,24 @@ Configuring Infisical requires setting some environment variables. There is a fi
|
||||
|
||||
| Variable | Description | Default Value |
|
||||
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------- |
|
||||
| `PRIVATE_KEY` | ❗️ NaCl-generated server secret key | `None` |
|
||||
| `PUBLIC_KEY` | ❗️ NaCl-generated server public key | `None` |
|
||||
| `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` |
|
||||
| `JWT_SIGNUP_SECRET` | ❗️JWT token secret | `None` |
|
||||
| `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` |
|
||||
| `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` |
|
||||
| `PRIVATE_KEY` | ❗️ NaCl-generated server secret key | `None` |
|
||||
| `PUBLIC_KEY` | ❗️ NaCl-generated server public key | `None` |
|
||||
| `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` |
|
||||
| `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` |
|
||||
| `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` |
|
||||
| `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` |
|
||||
| `JWT_SIGNUP_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `15m` |
|
||||
| `JWT_REFRESH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `90d` |
|
||||
| `JWT_AUTH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `10d` |
|
||||
| `EMAIL_TOKEN_LIFETIME` | Email OTP/magic-link lifetime expressed in seconds | `86400` |
|
||||
| `MONGO_URL` | ❗️ MongoDB instance connection string either to container instance or MongoDB Cloud | `None` |
|
||||
| `MONGO_URL` | ❗️ MongoDB instance connection string either to container instance or MongoDB Cloud | `None` |
|
||||
| `MONGO_USERNAME` | MongoDB username if using container | `None` |
|
||||
| `MONGO_PASSWORD` | MongoDB password if using container | `None` |
|
||||
| `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` |
|
||||
| `SMT_HOST` | Whether the user joined the community | `smtp.gmail.com` |
|
||||
| `SMTP_NAME` | Hostname to connect to for establishing SMTP connections (e.g. `Team`) | `None` |
|
||||
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` |
|
||||
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` |
|
||||
| `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` |
|
||||
| `SMTP_HOST` | Hostname to connect to for establishing SMTP connections | `smtp.gmail.com` |
|
||||
| `SMTP_NAME` | Name label to be used in From field (e.g. `Team`) | `None` |
|
||||
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` |
|
||||
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` |
|
||||
| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` |
|
||||
| `OAUTH_CLIENT_SECRET_HEROKU` | OAuth client secret for Heroku integration | `None` |
|
||||
| `OAUTH_TOKEN_URL_HEROKU` | OAuth token URL for Heroku integration | `None` |
|
||||
|
54
docs/self-hosting/deployments/kubernetes.mdx
Normal file
54
docs/self-hosting/deployments/kubernetes.mdx
Normal file
@ -0,0 +1,54 @@
|
||||
---
|
||||
title: "Kubernetes"
|
||||
description: "Deploy with Kubernetes"
|
||||
---
|
||||
|
||||
<Info>
|
||||
Self-host vs. Infisical Cloud
|
||||
|
||||
Self-hosting Infisical means managing the service yourself, taking care of upgrades, scaling, security, etc.
|
||||
|
||||
If you're less technical and looking for a hands-free experience with minimal overhead then we recommend Infisical Cloud.
|
||||
|
||||
</Info>
|
||||
|
||||
**Prerequisites**
|
||||
- You have understanding of [Kubernetes](https://kubernetes.io/)
|
||||
- You have understanding of [Helm package manager](https://helm.sh/)
|
||||
- You have [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) installed and connected to your kubernetes cluster
|
||||
|
||||
|
||||
#### 1. Fill our environment variables
|
||||
|
||||
Before you can deploy the Helm chart, you must fill out the required environment variables. To do so, please either download or copy the
|
||||
contents of [this file](https://raw.githubusercontent.com/Infisical/infisical/main/helm-charts/infisical/values.yaml) to a `.yaml` file.
|
||||
_Refer to the available [environment variables](../../self-hosting/configuration/envars)_
|
||||
|
||||
Once you have a local copy of the values file, fill our the required environment variables and save the file.
|
||||
|
||||
|
||||
#### 2. Install Infisical Helm repository
|
||||
|
||||
```bash
|
||||
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
|
||||
|
||||
helm repo update
|
||||
```
|
||||
|
||||
#### 3. Install the Helm chart
|
||||
|
||||
By default, the helm chart will be installed on your default namespace. If you wish to install the Chart on a different namespace, you may specify
|
||||
that by adding the `--namespace <namespace-to-install-to>` to your `helm install` command.
|
||||
|
||||
```bash
|
||||
## Installs to default namespace
|
||||
helm install infisical-helm-charts/infisical --values <path to the values.yaml you downloaded/created in step 2>
|
||||
```
|
||||
|
||||
<Note>
|
||||
If you have not filled out all of the required environment variables, you will see an error message prompting you to
|
||||
do so.
|
||||
</Note>
|
||||
|
||||
4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`.
|
||||
Note: Please allow an additional time (2 minutes) for the frontend pods to be fully ready.
|
@ -51,4 +51,4 @@ nano .env
|
||||
docker-compose -f docker-compose.yml up -d
|
||||
```
|
||||
|
||||
5. Your Infisical installation is complete and should be running on port 8080. Please note that the containers are not exposed to the internet and only bind to the localhost. It's up to you to configure a firewall, SSL certificates, and implement any additional security measures.
|
||||
5. Your Infisical installation is complete and should be running on [http://localhost:8080](http://localhost:8080). Please note that the containers are not exposed to the internet and only bind to the localhost. It's up to you to configure a firewall, SSL certificates, and implement any additional security measures.
|
||||
|
@ -9,17 +9,22 @@ Self-hosting Infisical means managing the service yourself, taking care of upgra
|
||||
|
||||
If you're less technical and looking for a hands-free experience with minimal overhead then we recommend Infisical Cloud.
|
||||
|
||||
Infisical Cloud also comes with some extra features unavailabe in the self-hosted edition. You can find more information about Infisical Cloud's offering on the pricing page.
|
||||
Infisical Cloud also comes with some extra features unavailable in the self-hosted edition. You can find more information about Infisical Cloud's offering on the pricing page.
|
||||
|
||||
</Info>
|
||||
|
||||
## Deployment options
|
||||
|
||||
Infisical can be deployed on a Linux VM with docker-compose. We're rolling out more specific deployment options for DigitalOcean, AWS, GCP, and Azure soon.
|
||||
Infisical can be deployed on a Linux VM with docker-compose and Kubernetes. We're rolling out more specific deployment options for DigitalOcean, AWS, GCP, and Azure soon.
|
||||
|
||||
Options:
|
||||
|
||||
- [Linux VM](/self-hosting/deployments/linux)
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Any Linux" icon="square-1" color="#ea5a0c" href="/self-hosting/deployments/linux">
|
||||
Deploy to any Linux with Docker
|
||||
</Card>
|
||||
<Card title="Kubernetes" icon="square-2" color="#0285c7" href="/self-hosting/deployments/kubernetes">
|
||||
Deploy to your Kubernetes cluster
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Telemetry
|
||||
|
||||
|
@ -1,10 +1,21 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"plugins": [
|
||||
"simple-import-sort"
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"next",
|
||||
"next/core-web-vitals"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["simple-import-sort", "@typescript-eslint"],
|
||||
"rules": {
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
|
||||
"simple-import-sort/exports": "warn",
|
||||
"simple-import-sort/imports": [
|
||||
"warn",
|
||||
@ -18,15 +29,9 @@
|
||||
"^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)"
|
||||
],
|
||||
// Packages `react` related packages
|
||||
[
|
||||
"^react",
|
||||
"^next",
|
||||
"^@?\\w"
|
||||
],
|
||||
["^react", "^next", "^@?\\w"],
|
||||
// Internal packages.
|
||||
[
|
||||
"^~(/.*|$)"
|
||||
],
|
||||
["^~(/.*|$)"],
|
||||
// Relative imports
|
||||
[
|
||||
"^\\.\\.(?!/?$)",
|
||||
@ -36,11 +41,9 @@
|
||||
"^\\./?$"
|
||||
],
|
||||
// Style imports.
|
||||
[
|
||||
"^.+\\.?(css|scss)$"
|
||||
]
|
||||
["^.+\\.?(css|scss)$"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": true
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
|
64
frontend/Dockerfile
Normal file
64
frontend/Dockerfile
Normal file
@ -0,0 +1,64 @@
|
||||
ARG POSTHOG_HOST=https://app.posthog.com
|
||||
ARG POSTHOG_API_KEY=posthog-api-key
|
||||
|
||||
FROM node:16-alpine AS deps
|
||||
# Install dependencies only when needed. Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
# RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Copy over dependency files
|
||||
COPY package.json package-lock.json next.config.js ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci --only-production
|
||||
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM node:16-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependencies
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
# Copy all files
|
||||
COPY . .
|
||||
|
||||
ENV NODE_ENV production
|
||||
ENV NEXT_PUBLIC_ENV production
|
||||
ARG POSTHOG_HOST
|
||||
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST
|
||||
ARG POSTHOG_API_KEY
|
||||
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
|
||||
|
||||
# Build
|
||||
RUN npm run build
|
||||
|
||||
|
||||
# Production image
|
||||
FROM node:16-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
RUN mkdir -p /app/.next/cache/images && chown nextjs:nodejs /app/.next/cache/images
|
||||
VOLUME /app/.next/cache/images
|
||||
|
||||
ARG POSTHOG_API_KEY
|
||||
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
|
||||
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
|
||||
|
||||
COPY --chown=nextjs:nodejs --chmod=555 scripts ./scripts
|
||||
COPY --from=builder /app/public ./public
|
||||
RUN chown nextjs:nodejs ./public/data
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
|
||||
CMD ["/app/scripts/start.sh"]
|
@ -1,20 +1,22 @@
|
||||
This is the client repository for Infisical.
|
||||
|
||||
## Before you get started with development locally
|
||||
## Before you get started with development locally
|
||||
|
||||
Please ensure you have Docker and Docker Compose installed for your OS.
|
||||
|
||||
### Steps to start server
|
||||
### Steps to start server
|
||||
|
||||
- `CD` into the repo
|
||||
- run command `docker-compose -f docker-compose.dev.yml up --build --force-recreate`
|
||||
- Vist localhost:3000 and the website should be live
|
||||
- Visit localhost:3000 and the website should be live
|
||||
|
||||
### Steps to shutdown this Docker compose
|
||||
|
||||
### Steps to shutdown this Docker compose
|
||||
- `CD` into this repo
|
||||
- run command `docker-compose -f docker-compose-dev.yml down`
|
||||
- run command `docker-compose -f docker-compose.dev.yml down`
|
||||
|
||||
### Notes
|
||||
Any changes made to local files in the `/components`, `/pages`, `styles` will be hot reloaded. If would like like to watch for other files or folders live, please add them to the docker volume.
|
||||
|
||||
Any changes made to local files in the `/components`, `/pages`, `/styles` will be hot reloaded. If would like like to watch for other files or folders live, please add them to the docker volume.
|
||||
|
||||
You will also need to ensure that a .env.local file exists with all required environment variables
|
||||
|
||||
|
||||
|
@ -1,85 +1,84 @@
|
||||
import { useEffect,useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { publicPaths } from "~/const";
|
||||
import checkAuth from "~/pages/api/auth/CheckAuth";
|
||||
|
||||
import { publicPaths } from "../const";
|
||||
|
||||
// #TODO: finish spinner only when the data loads fully
|
||||
// #TODO: Redirect somewhere if the page does not exist
|
||||
|
||||
export default function RouteGuard({ children }) {
|
||||
const router = useRouter();
|
||||
const [authorized, setAuthorized] = useState(false);
|
||||
const router = useRouter();
|
||||
const [authorized, setAuthorized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// on initial load - run auth check
|
||||
(async () => {
|
||||
await authCheck(router.asPath);
|
||||
})();
|
||||
useEffect(() => {
|
||||
// on initial load - run auth check
|
||||
(async () => {
|
||||
await authCheck(router.asPath);
|
||||
})();
|
||||
|
||||
// on route change start - hide page content by setting authorized to false
|
||||
// #TODO: add the loading page when not yet authorized.
|
||||
const hideContent = () => setAuthorized(false);
|
||||
// const onError = () => setAuthorized(true)
|
||||
router.events.on("routeChangeStart", hideContent);
|
||||
// router.events.on("routeChangeError", onError);
|
||||
// on route change start - hide page content by setting authorized to false
|
||||
// #TODO: add the loading page when not yet authorized.
|
||||
const hideContent = () => setAuthorized(false);
|
||||
// const onError = () => setAuthorized(true)
|
||||
router.events.on("routeChangeStart", hideContent);
|
||||
// router.events.on("routeChangeError", onError);
|
||||
|
||||
// on route change complete - run auth check
|
||||
router.events.on("routeChangeComplete", authCheck);
|
||||
// on route change complete - run auth check
|
||||
router.events.on("routeChangeComplete", authCheck);
|
||||
|
||||
// unsubscribe from events in useEffect return function
|
||||
return () => {
|
||||
router.events.off("routeChangeStart", hideContent);
|
||||
router.events.off("routeChangeComplete", authCheck);
|
||||
// router.events.off("routeChangeError", onError);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
// unsubscribe from events in useEffect return function
|
||||
return () => {
|
||||
router.events.off("routeChangeStart", hideContent);
|
||||
router.events.off("routeChangeComplete", authCheck);
|
||||
// router.events.off("routeChangeError", onError);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* redirect to login page if accessing a private page and not logged in
|
||||
* @param {*} url - the url of the page we are trying to go to
|
||||
*/
|
||||
async function authCheck(url) {
|
||||
// Make sure that we don't redirect when the user is on the following pages.
|
||||
const path = "/" + url.split("?")[0].split("/")[1];
|
||||
/**
|
||||
* redirect to login page if accessing a private page and not logged in
|
||||
* @param {*} url - the url of the page we are trying to go to
|
||||
*/
|
||||
async function authCheck(url) {
|
||||
// Make sure that we don't redirect when the user is on the following pages.
|
||||
const path = "/" + url.split("?")[0].split("/")[1];
|
||||
|
||||
// Check if the user is authenticated
|
||||
const response = await checkAuth();
|
||||
// #TODO: figure our why sometimes it doesn't output a response
|
||||
if (!publicPaths.includes(path)) {
|
||||
try {
|
||||
if (response.status !== 200) {
|
||||
router.push("/login");
|
||||
console.log("Unauthorized to access.");
|
||||
setAuthorized(false);
|
||||
} else {
|
||||
setAuthorized(true);
|
||||
console.log("Authorized to access.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"Error (probably the authCheck route is stuck again...):",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if the user is authenticated
|
||||
const response = await checkAuth();
|
||||
// #TODO: figure our why sometimes it doesn't output a response
|
||||
if (!publicPaths.includes(path)) {
|
||||
try {
|
||||
if (response.status !== 200) {
|
||||
router.push("/login");
|
||||
console.log("Unauthorized to access.");
|
||||
setAuthorized(false);
|
||||
} else {
|
||||
setAuthorized(true);
|
||||
console.log("Authorized to access.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"Error (probably the authCheck route is stuck again...):",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (authorized) {
|
||||
return children;
|
||||
} else {
|
||||
return (
|
||||
<div className="w-screen h-screen bg-bunker-800 flex items-center justify-center">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="google logo"
|
||||
></Image>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (authorized) {
|
||||
return children;
|
||||
} else {
|
||||
return (
|
||||
<div className="w-screen h-screen bg-bunker-800 flex items-center justify-center">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="google logo"
|
||||
></Image>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,19 @@
|
||||
import posthog from "posthog-js";
|
||||
|
||||
import { ENV, POSTHOG_API_KEY, POSTHOG_HOST, TELEMETRY_ENABLED } from "../utilities/config";
|
||||
import {
|
||||
ENV,
|
||||
POSTHOG_API_KEY,
|
||||
POSTHOG_HOST,
|
||||
} from "../utilities/config";
|
||||
|
||||
export const initPostHog = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
if (ENV == "production" && TELEMETRY_ENABLED) {
|
||||
posthog.init(POSTHOG_API_KEY, {
|
||||
api_host: POSTHOG_HOST,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
if (ENV == "production" && TELEMETRY_CAPTURING_ENABLED) { // eslint-disable-line
|
||||
posthog.init(POSTHOG_API_KEY, {
|
||||
api_host: POSTHOG_HOST,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return posthog;
|
||||
return posthog;
|
||||
};
|
||||
|
@ -1,19 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export default function Error({ text }) {
|
||||
return (
|
||||
<div className="relative bg-red-500 opacity-100 border flex flex-row justify-center m-auto items-center w-fit rounded-full mb-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationTriangle}
|
||||
className="text-white mt-1.5 mb-2 mx-2"
|
||||
/>
|
||||
{text && (
|
||||
<p className="relative top-0 text-white mr-2 text-sm py-1">
|
||||
{text}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
17
frontend/components/basic/Error.tsx
Normal file
17
frontend/components/basic/Error.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export default function Error({ text }: { text: string }): JSX.Element {
|
||||
return (
|
||||
<div className="relative bg-red-500 opacity-100 border flex flex-row justify-center m-auto items-center w-fit rounded-full mb-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationTriangle}
|
||||
className="text-white mt-1.5 mb-2 mx-2"
|
||||
/>
|
||||
{text && (
|
||||
<p className="relative top-0 text-white mr-2 text-sm py-1">{text}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
import React from "react";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faCircle,
|
||||
faCircleExclamation,
|
||||
faE,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import guidGenerator from "../utilities/randomId";
|
||||
import Error from "./Error";
|
||||
|
||||
const InputField = (props) => {
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
if (props.static === true) {
|
||||
return (
|
||||
<div className="flex flex-col my-2 md:my-4 justify-center w-full max-w-md">
|
||||
<p className="text-sm font-semibold text-gray-400 mb-0.5">
|
||||
{props.label}
|
||||
</p>
|
||||
{props.text && (
|
||||
<p className="text-xs text-gray-400 mb-2">{props.text}</p>
|
||||
)}
|
||||
<input
|
||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||
type={props.type}
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
required={props.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"
|
||||
name={props.name}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5">
|
||||
<p className="text-sm font-semibold mr-1">{props.label}</p>
|
||||
{/* {props.label == "Password" && router.asPath != "/login" && (
|
||||
<div className="mb-0.5 relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||
<FontAwesomeIcon
|
||||
icon={faCircleExclamation}
|
||||
className={`text-sm peer ${
|
||||
props.error && "text-red"
|
||||
}`}
|
||||
/>
|
||||
<span className="absolute hidden peer-hover:block duration-200 w-60 -left-28 -top-2 -translate-y-full px-2 py-2 bg-gray-700 rounded-md text-center text-gray-200 text-sm after:content-[''] after:absolute after:left-1/2 after:top-[100%] after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-b-transparent after:border-t-gray-700">
|
||||
The password should contain at least 8
|
||||
characters including at least 1 lowercase
|
||||
character, uppercase character, number, and a
|
||||
special character.
|
||||
</span>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
|
||||
props.error ? "border-red" : "border-mineshaft-500"
|
||||
} rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||
type={passwordVisible == false ? props.type : "text"}
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
required={props.isRequired}
|
||||
className={`${
|
||||
props.blurred
|
||||
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400"
|
||||
: ""
|
||||
} ${
|
||||
props.error
|
||||
? "focus:ring-red/50"
|
||||
: "focus:ring-primary/50"
|
||||
} relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
|
||||
name={props.name}
|
||||
spellCheck="false"
|
||||
/>
|
||||
{props.label?.includes("Password") && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setPasswordVisible(!passwordVisible);
|
||||
}}
|
||||
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
|
||||
>
|
||||
{passwordVisible ? (
|
||||
<FontAwesomeIcon icon={faEyeSlash} />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faEye} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{props.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">
|
||||
<p className="ml-2"></p>
|
||||
{props.value
|
||||
.split("")
|
||||
.slice(0, 54)
|
||||
.map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* {props.error && (
|
||||
<div className="absolute z-20 flex items-end justify-end mt-4 mr-1.5 self-end">
|
||||
<Error />
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
{props.error && (
|
||||
<p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">
|
||||
{props.errorText}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default React.memo(InputField);
|
137
frontend/components/basic/InputField.tsx
Normal file
137
frontend/components/basic/InputField.tsx
Normal file
@ -0,0 +1,137 @@
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { faCircle, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import guidGenerator from "../utilities/randomId";
|
||||
|
||||
interface InputFieldProps {
|
||||
static?: boolean;
|
||||
label: string;
|
||||
type: string;
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
isRequired: boolean;
|
||||
disabled?: boolean;
|
||||
error?: boolean;
|
||||
text?: string;
|
||||
name?: string;
|
||||
blurred?: boolean;
|
||||
errorText?: string;
|
||||
onChangeHandler: (value: string) => void;
|
||||
}
|
||||
|
||||
const InputField = (props: InputFieldProps) => {
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
if (props.static === true) {
|
||||
return (
|
||||
<div className="flex flex-col my-2 md:my-4 justify-center w-full max-w-md">
|
||||
<p className="text-sm font-semibold text-gray-400 mb-0.5">
|
||||
{props.label}
|
||||
</p>
|
||||
{props.text && (
|
||||
<p className="text-xs text-gray-400 mb-2">{props.text}</p>
|
||||
)}
|
||||
<input
|
||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||
type={props.type}
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
required={props.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"
|
||||
name={props.name}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5">
|
||||
<p className="text-sm font-semibold mr-1">{props.label}</p>
|
||||
{/* {props.label == "Password" && router.asPath != "/login" && (
|
||||
<div className="mb-0.5 relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||
<FontAwesomeIcon
|
||||
icon={faCircleExclamation}
|
||||
className={`text-sm peer ${
|
||||
props.error && "text-red"
|
||||
}`}
|
||||
/>
|
||||
<span className="absolute hidden peer-hover:block duration-200 w-60 -left-28 -top-2 -translate-y-full px-2 py-2 bg-gray-700 rounded-md text-center text-gray-200 text-sm after:content-[''] after:absolute after:left-1/2 after:top-[100%] after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-b-transparent after:border-t-gray-700">
|
||||
The password should contain at least 8
|
||||
characters including at least 1 lowercase
|
||||
character, uppercase character, number, and a
|
||||
special character.
|
||||
</span>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
|
||||
props.error ? "border-red" : "border-mineshaft-500"
|
||||
} rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||
type={passwordVisible === false ? props.type : "text"}
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
required={props.isRequired}
|
||||
className={`${
|
||||
props.blurred
|
||||
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400"
|
||||
: ""
|
||||
} ${
|
||||
props.error ? "focus:ring-red/50" : "focus:ring-primary/50"
|
||||
} relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
|
||||
name={props.name}
|
||||
spellCheck="false"
|
||||
/>
|
||||
{props.label?.includes("Password") && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setPasswordVisible(!passwordVisible);
|
||||
}}
|
||||
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
|
||||
>
|
||||
{passwordVisible ? (
|
||||
<FontAwesomeIcon icon={faEyeSlash} />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faEye} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{props.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">
|
||||
<p className="ml-2"></p>
|
||||
{props.value
|
||||
.split("")
|
||||
.slice(0, 54)
|
||||
.map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* {props.error && (
|
||||
<div className="absolute z-20 flex items-end justify-end mt-4 mr-1.5 self-end">
|
||||
<Error />
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
{props.error && (
|
||||
<p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">
|
||||
{props.errorText}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default React.memo(InputField);
|
430
frontend/components/basic/Layout.tsx
Normal file
430
frontend/components/basic/Layout.tsx
Normal file
@ -0,0 +1,430 @@
|
||||
/* eslint-disable no-unexpected-multiline */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faBookOpen,
|
||||
faGear,
|
||||
faKey,
|
||||
faMobile,
|
||||
faPlug,
|
||||
faUser,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import getOrganizations from "~/pages/api/organization/getOrgs";
|
||||
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
|
||||
import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers";
|
||||
import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace";
|
||||
import createWorkspace from "~/pages/api/workspace/createWorkspace";
|
||||
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
|
||||
import uploadKeys from "~/pages/api/workspace/uploadKeys";
|
||||
import checkUserAction from "~/pages/api/userActions/checkUserAction";
|
||||
|
||||
import NavBarDashboard from "../navigation/NavBarDashboard";
|
||||
import { tempLocalStorage } from "../utilities/checks/tempLocalStorage";
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
} from "../utilities/cryptography/crypto";
|
||||
import Button from "./buttons/Button";
|
||||
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
|
||||
import Listbox from "./Listbox";
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
const router = useRouter();
|
||||
const [workspaceList, setWorkspaceList] = useState([]);
|
||||
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
|
||||
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
|
||||
const [newWorkspaceName, setNewWorkspaceName] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [hasUserClickedSlack, setHasUserClickedSlack] = useState(false);
|
||||
const [hasUserClickedIntro, setHasUserClickedIntro] = useState(false);
|
||||
const [hasUserStarred, setHasUserStarred] = useState(false);
|
||||
const [usersInOrg, setUsersInOrg] = useState(false);
|
||||
const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] = useState(0);
|
||||
|
||||
function closeModal() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
setIsOpen(true);
|
||||
}
|
||||
|
||||
// TODO: what to do about the fact that 2ids can have the same name
|
||||
|
||||
/**
|
||||
* When a user creates a new workspace, redirect them to the page of the new workspace.
|
||||
* @param {*} workspaceName
|
||||
*/
|
||||
async function submitModal(workspaceName: string, addAllUsers: boolean) {
|
||||
setLoading(true);
|
||||
// timeout code.
|
||||
setTimeout(() => setLoading(false), 1500);
|
||||
|
||||
try {
|
||||
const workspaces = await getWorkspaces();
|
||||
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
|
||||
if (!currentWorkspaces.includes(workspaceName)) {
|
||||
const newWorkspace = await createWorkspace({
|
||||
workspaceName,
|
||||
organizationId: tempLocalStorage("orgData.id"),
|
||||
});
|
||||
const newWorkspaceId = newWorkspace._id;
|
||||
|
||||
if (addAllUsers) {
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId: tempLocalStorage("orgData.id"),
|
||||
});
|
||||
orgUsers.map(async (user: any) => {
|
||||
if (user.status == "accepted") {
|
||||
const result = await addUserToWorkspace(
|
||||
user.user.email,
|
||||
newWorkspaceId
|
||||
);
|
||||
if (result?.invitee && result?.latestKey) {
|
||||
const PRIVATE_KEY = tempLocalStorage("PRIVATE_KEY");
|
||||
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: result.latestKey.encryptedKey,
|
||||
nonce: result.latestKey.nonce,
|
||||
publicKey: result.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: result.invitee.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
}) as { ciphertext: string; nonce: string };
|
||||
|
||||
uploadKeys(
|
||||
newWorkspaceId,
|
||||
result.invitee._id,
|
||||
ciphertext,
|
||||
nonce
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
router.push("/dashboard/" + newWorkspaceId + "?Development");
|
||||
setIsOpen(false);
|
||||
setNewWorkspaceName("");
|
||||
} else {
|
||||
console.error("A project with this name already exists.");
|
||||
setError(true);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError(true);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
href:
|
||||
"/dashboard/" +
|
||||
workspaceMapping[workspaceSelected as any] +
|
||||
"?Development",
|
||||
title: "Secrets",
|
||||
emoji: <FontAwesomeIcon icon={faKey} />,
|
||||
},
|
||||
{
|
||||
href: "/users/" + workspaceMapping[workspaceSelected as any],
|
||||
title: "Members",
|
||||
emoji: <FontAwesomeIcon icon={faUser} />,
|
||||
},
|
||||
{
|
||||
href: "/integrations/" + workspaceMapping[workspaceSelected as any],
|
||||
title: "Integrations",
|
||||
emoji: <FontAwesomeIcon icon={faPlug} />,
|
||||
},
|
||||
{
|
||||
href: "/settings/project/" + workspaceMapping[workspaceSelected as any],
|
||||
title: "Project Settings",
|
||||
emoji: <FontAwesomeIcon icon={faGear} />,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
// Put a user in a workspace if they're not in one yet
|
||||
const putUserInWorkSpace = async () => {
|
||||
if (tempLocalStorage("orgData.id") === "") {
|
||||
const userOrgs = await getOrganizations();
|
||||
localStorage.setItem("orgData.id", userOrgs[0]._id);
|
||||
}
|
||||
|
||||
const orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId: tempLocalStorage("orgData.id"),
|
||||
});
|
||||
const userWorkspaces = orgUserProjects;
|
||||
if (
|
||||
userWorkspaces.length == 0 &&
|
||||
router.asPath != "/noprojects" &&
|
||||
!router.asPath.includes("settings")
|
||||
) {
|
||||
router.push("/noprojects");
|
||||
} else if (router.asPath != "/noprojects") {
|
||||
const intendedWorkspaceId = router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0];
|
||||
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
||||
if (
|
||||
intendedWorkspaceId != "heroku" &&
|
||||
!userWorkspaces
|
||||
.map((workspace: { _id: string }) => workspace._id)
|
||||
.includes(intendedWorkspaceId)
|
||||
) {
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id + "?Development");
|
||||
} else {
|
||||
setWorkspaceList(
|
||||
userWorkspaces.map((workspace: any) => workspace.name)
|
||||
);
|
||||
setWorkspaceMapping(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace: any) => [
|
||||
workspace.name,
|
||||
workspace._id,
|
||||
])
|
||||
) as any
|
||||
);
|
||||
setWorkspaceSelected(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace: any) => [
|
||||
workspace._id,
|
||||
workspace.name,
|
||||
])
|
||||
)[
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
putUserInWorkSpace();
|
||||
|
||||
const checkUserActionsFunction = async () => {
|
||||
let countActions = 0;
|
||||
const userActionSlack = await checkUserAction({
|
||||
action: "slack_cta_clicked",
|
||||
});
|
||||
setHasUserClickedSlack(userActionSlack ? true : false);
|
||||
if (userActionSlack) {
|
||||
countActions = countActions + 1;
|
||||
}
|
||||
|
||||
const userActionIntro = await checkUserAction({
|
||||
action: "intro_cta_clicked",
|
||||
});
|
||||
setHasUserClickedIntro(userActionIntro ? true : false);
|
||||
if (userActionIntro) {
|
||||
countActions = countActions + 1;
|
||||
}
|
||||
|
||||
const userActionStar = await checkUserAction({
|
||||
action: "star_cta_clicked",
|
||||
});
|
||||
setHasUserStarred(userActionStar ? true : false);
|
||||
if (userActionStar) {
|
||||
countActions = countActions + 1;
|
||||
}
|
||||
|
||||
const orgId = localStorage.getItem("orgData.id");
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId: orgId ? orgId : "",
|
||||
});
|
||||
setUsersInOrg(orgUsers.length > 1)
|
||||
if (orgUsers.length > 1) {
|
||||
countActions = countActions + 1;
|
||||
}
|
||||
console.log(123, countActions)
|
||||
setTotalOnboardingActionsDone(countActions);
|
||||
};
|
||||
console.log(`images/progress-${totalOnboardingActionsDone == 0 ? "0" : ""}${totalOnboardingActionsDone == 1 ? "14" : ""}${totalOnboardingActionsDone == 1 ? "28" : ""}${totalOnboardingActionsDone == 3 ? "43" : ""}${totalOnboardingActionsDone == 4 ? "57" : ""}.svg`)
|
||||
checkUserActionsFunction();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (
|
||||
workspaceMapping[Number(workspaceSelected)] &&
|
||||
`${workspaceMapping[Number(workspaceSelected)]}` !==
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
) {
|
||||
router.push(
|
||||
"/dashboard/" +
|
||||
workspaceMapping[Number(workspaceSelected)] +
|
||||
"?Development"
|
||||
);
|
||||
localStorage.setItem(
|
||||
"projectData.id",
|
||||
`${workspaceMapping[Number(workspaceSelected)]}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, [workspaceSelected]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed w-full hidden md:block flex flex-col h-screen">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.2.2/cdn.js" defer></script>
|
||||
<NavBarDashboard />
|
||||
<div className="flex flex-col md:flex-row flex-1">
|
||||
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
|
||||
<nav className="flex flex-col justify-between items-between h-full">
|
||||
{/* <div className="py-6"></div> */}
|
||||
<div>
|
||||
<div className="flex justify-center w-full mt-[4.5rem] mb-6 bg-bunker-600 h-20 flex-col items-center px-4">
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
PROJECT
|
||||
</div>
|
||||
{workspaceList.length > 0 ? (
|
||||
<Listbox
|
||||
selected={workspaceSelected}
|
||||
onChange={setWorkspaceSelected as any}
|
||||
data={workspaceList}
|
||||
buttonAction={openModal}
|
||||
text=""
|
||||
// workspaceMapping={workspaceMapping as any}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
text="Add Project"
|
||||
onButtonPressed={openModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ul>
|
||||
{workspaceList.length > 0 &&
|
||||
menuItems.map(({ href, title, emoji }) => (
|
||||
<li className="mt-0.5 mx-2" key={title}>
|
||||
{router.asPath.split("/")[1] === href.split("/")[1] &&
|
||||
(["project", "billing", "org", "personal"].includes(
|
||||
router.asPath.split("/")[2]
|
||||
)
|
||||
? router.asPath.split("/")[2] === href.split("/")[2]
|
||||
: true) ? (
|
||||
<div
|
||||
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
|
||||
>
|
||||
<div className="absolute top-0 my-1 ml-1 inset-0 bg-primary w-1 rounded-xl mr-1"></div>
|
||||
<p className="w-6 ml-4 mr-2 flex items-center justify-center text-lg">
|
||||
{emoji}
|
||||
</p>
|
||||
{title}
|
||||
</div>
|
||||
) : router.asPath == "/noprojects" ? (
|
||||
<div
|
||||
className={`flex p-2.5 text-white text-sm rounded`}
|
||||
>
|
||||
<p className="w-10 flex items-center justify-center text-lg">
|
||||
{emoji}
|
||||
</p>
|
||||
{title}
|
||||
</div>
|
||||
) : (
|
||||
<Link href={href}>
|
||||
<div
|
||||
className={`flex p-2.5 text-white text-sm rounded cursor-pointer hover:bg-primary-50/5`}
|
||||
>
|
||||
<p className="w-10 flex items-center justify-center text-lg">
|
||||
{emoji}
|
||||
</p>
|
||||
{title}
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="w-full mt-40 mb-4 px-2">
|
||||
{router.asPath.split("/")[1] === "home" ? (
|
||||
<div
|
||||
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
|
||||
>
|
||||
<div className="absolute top-0 my-1 ml-1 inset-0 bg-primary w-1 rounded-xl mr-1"></div>
|
||||
<p className="w-6 ml-4 mr-2 flex items-center justify-center text-lg">
|
||||
<FontAwesomeIcon icon={faBookOpen} />
|
||||
</p>
|
||||
Infisical Guide
|
||||
<img
|
||||
src={`/images/progress-${totalOnboardingActionsDone == 0 ? "0" : ""}${totalOnboardingActionsDone == 1 ? "14" : ""}${totalOnboardingActionsDone == 1 ? "28" : ""}${totalOnboardingActionsDone == 3 ? "43" : ""}${totalOnboardingActionsDone == 4 ? "57" : ""}.svg`}
|
||||
height={58}
|
||||
width={58}
|
||||
alt="progress bar"
|
||||
className="absolute right-2 -top-2"
|
||||
></img>
|
||||
</div>
|
||||
) : (
|
||||
<Link
|
||||
href={`/home/` + workspaceMapping[workspaceSelected as any]}
|
||||
>
|
||||
<div
|
||||
className={`relative flex p-2.5 overflow-visible text-white h-10 text-sm rounded cursor-pointer bg-white/10 hover:bg-primary-50/[0.15] mt-max`}
|
||||
>
|
||||
<p className="w-10 flex items-center justify-center text-lg">
|
||||
<FontAwesomeIcon icon={faBookOpen} />
|
||||
</p>
|
||||
Infisical Guide
|
||||
<img
|
||||
src="/images/progress-75.svg"
|
||||
height={58}
|
||||
width={58}
|
||||
alt="progress bar"
|
||||
className="absolute right-2 -top-2"
|
||||
></img>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<AddWorkspaceDialog
|
||||
isOpen={isOpen}
|
||||
closeModal={closeModal}
|
||||
submitModal={submitModal}
|
||||
workspaceName={newWorkspaceName}
|
||||
setWorkspaceName={setNewWorkspaceName}
|
||||
error={error}
|
||||
loading={loading}
|
||||
/>
|
||||
<main className="flex-1 bg-bunker-800">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faMobile}
|
||||
className="text-gray-300 text-7xl mb-8"
|
||||
/>
|
||||
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
|
||||
{" "}
|
||||
To use Infisical, please log in through a device with larger
|
||||
dimensions.{" "}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
import React from "react";
|
||||
import { Fragment } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { faAngleDown,faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
|
||||
/**
|
||||
* This is the component that we use for drop down lists.
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function ListBox({
|
||||
selected,
|
||||
onChange,
|
||||
data,
|
||||
text,
|
||||
buttonAction,
|
||||
width,
|
||||
workspaceMapping = [],
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Listbox value={selected} onChange={onChange}>
|
||||
<div className="relative">
|
||||
<Listbox.Button
|
||||
className={`text-gray-400 relative ${
|
||||
width == "full" ? "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`}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
{text}
|
||||
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
|
||||
{" "}
|
||||
{selected}
|
||||
</span>
|
||||
</div>
|
||||
{data && (
|
||||
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5"/>
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Button>
|
||||
{data && (
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="border border-mineshaft-700 z-50 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">
|
||||
{data.map((person, personIdx) => (
|
||||
<Listbox.Option
|
||||
key={personIdx}
|
||||
className={({ active, selected }) =>
|
||||
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
|
||||
selected
|
||||
? "bg-white/10 text-gray-400 font-bold"
|
||||
: ""
|
||||
} ${
|
||||
active & !selected
|
||||
? "bg-white/5 text-mineshaft-200 cursor-pointer"
|
||||
: "text-gray-400"
|
||||
} `
|
||||
}
|
||||
value={person}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span
|
||||
className={`block truncate text-primary${
|
||||
selected
|
||||
? "font-medium"
|
||||
: "font-normal"
|
||||
}`}
|
||||
>
|
||||
{person}
|
||||
</span>
|
||||
{selected ? (
|
||||
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md ml-1"/>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
{buttonAction && (
|
||||
<button
|
||||
onClick={buttonAction}
|
||||
className="cursor-pointer w-full"
|
||||
>
|
||||
<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">
|
||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||
<FontAwesomeIcon icon={faPlus} className="text-lg"/>
|
||||
</span>
|
||||
Add Project
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
)}
|
||||
</div>
|
||||
</Listbox>
|
||||
);
|
||||
}
|
122
frontend/components/basic/Listbox.tsx
Normal file
122
frontend/components/basic/Listbox.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import React from "react";
|
||||
import { Fragment } from "react";
|
||||
import {
|
||||
faAngleDown,
|
||||
faCheck,
|
||||
faPlus,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
|
||||
interface ListBoxProps {
|
||||
selected: string;
|
||||
onChange: () => void;
|
||||
data: string[];
|
||||
text: string;
|
||||
buttonAction: () => void;
|
||||
isFull?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the component that we use for drop down lists.
|
||||
* @param {object} obj
|
||||
* @param {string} obj.selected - the item that is currently selected
|
||||
* @param {function} obj.onChange - what happends if you select the item inside a list
|
||||
* @param {string[]} obj.data - all the options available
|
||||
* @param {string} obj.text - the text that shows us in front of the select option
|
||||
* @param {function} obj.buttonAction - if there is a button at the bottom of the list, this is the action that happens when you click the button
|
||||
* @param {string} obj.width - button width
|
||||
* @returns
|
||||
*/
|
||||
export default function ListBox({
|
||||
selected,
|
||||
onChange,
|
||||
data,
|
||||
text,
|
||||
buttonAction,
|
||||
isFull,
|
||||
}: ListBoxProps): JSX.Element {
|
||||
return (
|
||||
<Listbox value={selected} onChange={onChange}>
|
||||
<div className="relative">
|
||||
<Listbox.Button
|
||||
className={`text-gray-400 relative ${
|
||||
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`}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
{text}
|
||||
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
|
||||
{" "}
|
||||
{selected}
|
||||
</span>
|
||||
</div>
|
||||
{data && (
|
||||
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5" />
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Button>
|
||||
{data && (
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="border border-mineshaft-700 z-50 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">
|
||||
{data.map((person, personIdx) => (
|
||||
<Listbox.Option
|
||||
key={personIdx}
|
||||
className={({ active, selected }) =>
|
||||
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
|
||||
selected ? "bg-white/10 text-gray-400 font-bold" : ""
|
||||
} ${
|
||||
active && !selected
|
||||
? "bg-white/5 text-mineshaft-200 cursor-pointer"
|
||||
: "text-gray-400"
|
||||
} `
|
||||
}
|
||||
value={person}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span
|
||||
className={`block truncate text-primary${
|
||||
selected ? "font-medium" : "font-normal"
|
||||
}`}
|
||||
>
|
||||
{person}
|
||||
</span>
|
||||
{selected ? (
|
||||
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md ml-1"
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
{buttonAction && (
|
||||
<button
|
||||
onClick={buttonAction}
|
||||
className="cursor-pointer w-full"
|
||||
>
|
||||
<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">
|
||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||
<FontAwesomeIcon icon={faPlus} className="text-lg" />
|
||||
</span>
|
||||
Add Project
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
)}
|
||||
</div>
|
||||
</Listbox>
|
||||
);
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
var classNames = require("classnames");
|
||||
|
||||
export default function Button({
|
||||
text,
|
||||
onButtonPressed,
|
||||
link,
|
||||
loading,
|
||||
color,
|
||||
size,
|
||||
icon,
|
||||
active = true,
|
||||
iconDisabled,
|
||||
textDisabled
|
||||
}) {
|
||||
let styleButton = classNames(
|
||||
"group m-auto md:m-0 inline-block rounded-md duration-200",
|
||||
|
||||
// Setting background colors and hover modes
|
||||
color == "mineshaft" && active && "bg-mineshaft-700 hover:bg-primary",
|
||||
color == "mineshaft" && !active && "bg-mineshaft",
|
||||
(color == "primary" || !color) && active && "bg-primary hover:opacity-80",
|
||||
(color == "primary" || !color) && !active && "bg-primary",
|
||||
color == "red" && "bg-red",
|
||||
|
||||
// Changing the opacity when active vs when not
|
||||
active ? "opacity-100 cursor-pointer" : "opacity-40",
|
||||
|
||||
// Setting the button sizes
|
||||
size == "md" && "h-10 w-full px-2 md:px-4",
|
||||
size == "lg" && "h-12 w-full px-2 md:px-8",
|
||||
!size && "md:py-1 px-3 md:px-8",
|
||||
size == "icon-md" && "h-10 w-10 flex items-center justify-center",
|
||||
size == "icon-sm" && "h-9 w-9 flex items-center justify-center",
|
||||
);
|
||||
|
||||
let styleMainDiv = classNames(
|
||||
"relative font-medium flex items-center",
|
||||
|
||||
// Setting the text color for the text and icon
|
||||
color == "mineshaft" && "text-gray-400",
|
||||
color != "mineshaft" && color != "red" && "text-black",
|
||||
color == "red" && "text-gray-200",
|
||||
active && color != "red" ? "group-hover:text-black" : "",
|
||||
|
||||
size == "icon" && "flex items-center justify-center",
|
||||
);
|
||||
|
||||
let textStyle = classNames(
|
||||
"relative duration-200",
|
||||
|
||||
// Show the loading sign if the loading indicator is on
|
||||
loading == true ? "opacity-0" : "opacity-100",
|
||||
size == "md" && "text-sm",
|
||||
size == "lg" && "text-lg"
|
||||
);
|
||||
|
||||
const button = (
|
||||
<button
|
||||
disabled={!active}
|
||||
onClick={onButtonPressed}
|
||||
className={styleButton}
|
||||
>
|
||||
<div className={styleMainDiv}>
|
||||
<div
|
||||
className={`${
|
||||
loading == true ? "opacity-100" : "opacity-0"
|
||||
} absolute flex items-center px-2 duration-200`}
|
||||
>
|
||||
<Image
|
||||
src="/images/loading/loadingblack.gif"
|
||||
height={25}
|
||||
width={42}
|
||||
alt="loading animation"
|
||||
className={`rounded-xl`}
|
||||
></Image>
|
||||
</div>
|
||||
{icon &&
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className={`flex my-auto font-extrabold ${size == "icon-sm" ? "text-sm" : "text-md"} ${(text || textDisabled) && "mr-2"}`}
|
||||
/>
|
||||
}
|
||||
{iconDisabled &&
|
||||
<FontAwesomeIcon
|
||||
icon={iconDisabled}
|
||||
className={`flex my-auto font-extrabold ${size == "icon-sm" ? "text-sm" : "text-md"} ${(text || textDisabled) && "mr-2"}`}
|
||||
/>
|
||||
}
|
||||
{(text || textDisabled) &&
|
||||
<p
|
||||
className={textStyle}
|
||||
>
|
||||
{active ? text : textDisabled}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
if (link) {
|
||||
return <Link href={link}>{button}</Link>;
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
137
frontend/components/basic/buttons/Button.tsx
Normal file
137
frontend/components/basic/buttons/Button.tsx
Normal file
@ -0,0 +1,137 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconProps,
|
||||
} from "@fortawesome/react-fontawesome";
|
||||
|
||||
const classNames = require("classnames");
|
||||
|
||||
type ButtonProps = {
|
||||
text: string;
|
||||
onButtonPressed: () => void;
|
||||
loading?: boolean;
|
||||
color?: string;
|
||||
size: string;
|
||||
icon?: IconProp;
|
||||
active?: boolean;
|
||||
iconDisabled?: string;
|
||||
textDisabled?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the main butto component in the app.
|
||||
* @param {object} props
|
||||
* @param {string} props.text - text inside the button
|
||||
* @param {function} props.onButtonPressed - the action that happens when the button is clicked
|
||||
* @param {boolean} props.loading - if a button is currently in the laoding state
|
||||
* @param {string} props.color - button color
|
||||
* @param {string} props.size - button size
|
||||
* @param {FontAwesomeIconProps} props.icon - the icon inside the button
|
||||
* @param {boolean} props.active - if the button is active or disabled
|
||||
* @param {FontAwesomeIconProps} props.text - the icon inside the button when it is disabled
|
||||
* @param {string} props.textDisable - text inside the button when it is disabled
|
||||
* @returns
|
||||
*/
|
||||
export default function Button(props: ButtonProps): JSX.Element {
|
||||
// Check if the button show always be 'active' - then true;
|
||||
// or if it should switch between 'active' and 'disabled' - then give the status
|
||||
const activityStatus =
|
||||
props.active || (props.text != "" && props.textDisabled == undefined);
|
||||
|
||||
const styleButton = classNames(
|
||||
"group m-auto md:m-0 inline-block rounded-md duration-200",
|
||||
|
||||
// Setting background colors and hover modes
|
||||
props.color == "mineshaft" &&
|
||||
activityStatus &&
|
||||
"bg-mineshaft-700 hover:bg-primary",
|
||||
props.color == "mineshaft" && !activityStatus && "bg-mineshaft",
|
||||
(props.color == "primary" || !props.color) &&
|
||||
activityStatus &&
|
||||
"bg-primary hover:opacity-80",
|
||||
(props.color == "primary" || !props.color) &&
|
||||
!activityStatus &&
|
||||
"bg-primary",
|
||||
props.color == "red" && "bg-red",
|
||||
|
||||
// Changing the opacity when active vs when not
|
||||
activityStatus ? "opacity-100 cursor-pointer" : "opacity-40",
|
||||
|
||||
// Setting the button sizes
|
||||
props.size == "md" && "h-10 w-full px-2 md:px-4",
|
||||
props.size == "lg" && "h-12 w-full px-2 md:px-8",
|
||||
!props.size && "md:py-1 px-3 md:px-8",
|
||||
props.size == "icon-md" && "h-10 w-10 flex items-center justify-center",
|
||||
props.size == "icon-sm" && "h-9 w-9 flex items-center justify-center"
|
||||
);
|
||||
|
||||
const styleMainDiv = classNames(
|
||||
"relative font-medium flex items-center",
|
||||
|
||||
// Setting the text color for the text and icon
|
||||
props.color == "mineshaft" && "text-gray-400",
|
||||
props.color != "mineshaft" && props.color != "red" && "text-black",
|
||||
props.color == "red" && "text-gray-200",
|
||||
activityStatus && props.color != "red" ? "group-hover:text-black" : "",
|
||||
|
||||
props.size == "icon" && "flex items-center justify-center"
|
||||
);
|
||||
|
||||
const textStyle = classNames(
|
||||
"relative duration-200",
|
||||
|
||||
// Show the loading sign if the loading indicator is on
|
||||
props.loading ? "opacity-0" : "opacity-100",
|
||||
props.size == "md" && "text-sm",
|
||||
props.size == "lg" && "text-lg"
|
||||
);
|
||||
|
||||
const button = (
|
||||
<button
|
||||
disabled={!activityStatus}
|
||||
onClick={props.onButtonPressed}
|
||||
className={styleButton}
|
||||
>
|
||||
<div className={styleMainDiv}>
|
||||
<div
|
||||
className={`${
|
||||
props.loading == true ? "opacity-100" : "opacity-0"
|
||||
} absolute flex items-center px-2 duration-200`}
|
||||
>
|
||||
<Image
|
||||
src="/images/loading/loadingblack.gif"
|
||||
height={25}
|
||||
width={42}
|
||||
alt="loading animation"
|
||||
className={`rounded-xl`}
|
||||
></Image>
|
||||
</div>
|
||||
{props.icon && (
|
||||
<FontAwesomeIcon
|
||||
icon={props.icon}
|
||||
className={`flex my-auto font-extrabold ${
|
||||
props.size == "icon-sm" ? "text-sm" : "text-md"
|
||||
} ${(props.text || props.textDisabled) && "mr-2"}`}
|
||||
/>
|
||||
)}
|
||||
{props.iconDisabled && (
|
||||
<FontAwesomeIcon
|
||||
icon={props.iconDisabled as IconProp}
|
||||
className={`flex my-auto font-extrabold ${
|
||||
props.size == "icon-sm" ? "text-sm" : "text-md"
|
||||
} ${(props.text || props.textDisabled) && "mr-2"}`}
|
||||
/>
|
||||
)}
|
||||
{(props.text || props.textDisabled) && (
|
||||
<p className={textStyle}>
|
||||
{activityStatus ? props.text : props.textDisabled}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
return button;
|
||||
}
|
@ -7,91 +7,92 @@ import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
|
||||
const AddIncidentContactDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
workspaceId,
|
||||
incidentContacts,
|
||||
setIncidentContacts,
|
||||
isOpen,
|
||||
closeModal,
|
||||
workspaceId,
|
||||
incidentContacts,
|
||||
setIncidentContacts,
|
||||
}) => {
|
||||
let [incidentContactEmail, setIncidentContactEmail] = useState("");
|
||||
let [incidentContactEmail, setIncidentContactEmail] = useState("");
|
||||
|
||||
const submit = () => {
|
||||
setIncidentContacts(
|
||||
incidentContacts?.length > 0
|
||||
? incidentContacts.concat([incidentContactEmail])
|
||||
: [incidentContactEmail]
|
||||
);
|
||||
addIncidentContact(localStorage.getItem("orgData.id"), incidentContactEmail);
|
||||
closeModal();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
const submit = () => {
|
||||
setIncidentContacts(
|
||||
incidentContacts?.length > 0
|
||||
? incidentContacts.concat([incidentContactEmail])
|
||||
: [incidentContactEmail]
|
||||
);
|
||||
addIncidentContact(
|
||||
localStorage.getItem("orgData.id"),
|
||||
incidentContactEmail
|
||||
);
|
||||
closeModal();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
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.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400"
|
||||
>
|
||||
Add an Incident Contact
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This contact will be notified in the
|
||||
unlikely event of a severe incident.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={
|
||||
setIncidentContactEmail
|
||||
}
|
||||
type="varName"
|
||||
value={incidentContactEmail}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6 max-w-max">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text="Add Incident Contact"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
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.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400"
|
||||
>
|
||||
Add an Incident Contact
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This contact will be notified in the unlikely event of a
|
||||
severe incident.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={setIncidentContactEmail}
|
||||
type="varName"
|
||||
value={incidentContactEmail}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6 max-w-max">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text="Add Incident Contact"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddIncidentContactDialog;
|
||||
|
@ -6,146 +6,133 @@ import Button from "../buttons/Button";
|
||||
import ListBox from "../Listbox";
|
||||
|
||||
const AddProjectMemberDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
data,
|
||||
email,
|
||||
workspaceId,
|
||||
setEmail,
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
data,
|
||||
email,
|
||||
workspaceId,
|
||||
setEmail,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||
{data?.length > 0 ? (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
Add a member to your project
|
||||
</Dialog.Title>
|
||||
) : (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
All the users in your organization
|
||||
are already invited.
|
||||
</Dialog.Title>
|
||||
)}
|
||||
<div className="mt-2 mb-4">
|
||||
{data?.length > 0 ? (
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
The user will receive an email
|
||||
with the instructions.
|
||||
</p>
|
||||
<div className="">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" +
|
||||
router.query.id
|
||||
)
|
||||
}
|
||||
>
|
||||
If you are looking to add users to your org,
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" +
|
||||
router.query.id +
|
||||
"?invite"
|
||||
)
|
||||
}
|
||||
>
|
||||
click here.
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500">
|
||||
Add more users to the
|
||||
organization first.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
{data?.length > 0 && (
|
||||
<ListBox
|
||||
selected={
|
||||
email ? email : data[0]
|
||||
}
|
||||
onChange={setEmail}
|
||||
data={data}
|
||||
width="full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-max">
|
||||
{data?.length > 0 ? (
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={submitModal}
|
||||
color="mineshaft"
|
||||
text="Add Member"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
router.push(
|
||||
"/settings/org/" +
|
||||
router.query.id
|
||||
)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Add Users to Organization"
|
||||
size="md"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||
{data?.length > 0 ? (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
Add a member to your project
|
||||
</Dialog.Title>
|
||||
) : (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
All the users in your organization are already invited.
|
||||
</Dialog.Title>
|
||||
)}
|
||||
<div className="mt-2 mb-4">
|
||||
{data?.length > 0 ? (
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
The user will receive an email with the instructions.
|
||||
</p>
|
||||
<div className="">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push("/settings/org/" + router.query.id)
|
||||
}
|
||||
>
|
||||
If you are looking to add users to your org,
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" + router.query.id + "?invite"
|
||||
)
|
||||
}
|
||||
>
|
||||
click here.
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500">
|
||||
Add more users to the organization first.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
{data?.length > 0 && (
|
||||
<ListBox
|
||||
selected={email ? email : data[0]}
|
||||
onChange={setEmail}
|
||||
data={data}
|
||||
width="full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-max">
|
||||
{data?.length > 0 ? (
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={submitModal}
|
||||
color="mineshaft"
|
||||
text="Add Member"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
router.push("/settings/org/" + router.query.id)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Add Users to Organization"
|
||||
size="md"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddProjectMemberDialog;
|
||||
|
@ -8,276 +8,248 @@ import nacl from "tweetnacl";
|
||||
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
|
||||
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||
|
||||
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
|
||||
import { envMapping } from "../../../public/data/frequentConstants";
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
} from "../../utilities/cryptography/crypto";
|
||||
import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
import ListBox from "../Listbox";
|
||||
|
||||
const envMapping = {
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
};
|
||||
|
||||
const expiryMapping = {
|
||||
"1 day": 86400,
|
||||
"7 days": 604800,
|
||||
"1 month": 2592000,
|
||||
"1 day": 86400,
|
||||
"7 days": 604800,
|
||||
"1 month": 2592000,
|
||||
};
|
||||
|
||||
const AddServiceTokenDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
workspaceId,
|
||||
workspaceName,
|
||||
isOpen,
|
||||
closeModal,
|
||||
workspaceId,
|
||||
workspaceName,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const [serviceToken, setServiceToken] = useState("");
|
||||
const [serviceTokenName, setServiceTokenName] = useState("");
|
||||
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
|
||||
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
|
||||
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
||||
const router = useRouter();
|
||||
const [serviceToken, setServiceToken] = useState("");
|
||||
const [serviceTokenName, setServiceTokenName] = useState("");
|
||||
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
|
||||
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
|
||||
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
||||
|
||||
const generateServiceToken = async () => {
|
||||
const latestFileKey = await getLatestFileKey(workspaceId);
|
||||
const generateServiceToken = async () => {
|
||||
const latestFileKey = await getLatestFileKey({ workspaceId });
|
||||
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: latestFileKey.latestKey.encryptedKey,
|
||||
nonce: latestFileKey.latestKey.nonce,
|
||||
publicKey: latestFileKey.latestKey.sender.publicKey,
|
||||
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
||||
});
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: latestFileKey.latestKey.encryptedKey,
|
||||
nonce: latestFileKey.latestKey.nonce,
|
||||
publicKey: latestFileKey.latestKey.sender.publicKey,
|
||||
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
||||
});
|
||||
|
||||
// generate new public/private key pair
|
||||
const pair = nacl.box.keyPair();
|
||||
const publicKey = nacl.util.encodeBase64(pair.publicKey);
|
||||
const privateKey = nacl.util.encodeBase64(pair.secretKey);
|
||||
// generate new public/private key pair
|
||||
const pair = nacl.box.keyPair();
|
||||
const publicKey = nacl.util.encodeBase64(pair.publicKey);
|
||||
const privateKey = nacl.util.encodeBase64(pair.secretKey);
|
||||
|
||||
// encrypt workspace key under newly-generated public key
|
||||
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey,
|
||||
privateKey,
|
||||
});
|
||||
// encrypt workspace key under newly-generated public key
|
||||
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey,
|
||||
privateKey,
|
||||
});
|
||||
|
||||
let newServiceToken = await addServiceToken({
|
||||
name: serviceTokenName,
|
||||
workspaceId,
|
||||
environment: envMapping[serviceTokenEnv],
|
||||
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
||||
publicKey,
|
||||
encryptedKey,
|
||||
nonce,
|
||||
});
|
||||
let newServiceToken = await addServiceToken({
|
||||
name: serviceTokenName,
|
||||
workspaceId,
|
||||
environment: envMapping[serviceTokenEnv],
|
||||
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
||||
publicKey,
|
||||
encryptedKey,
|
||||
nonce,
|
||||
});
|
||||
|
||||
const serviceToken = newServiceToken + "," + privateKey;
|
||||
setServiceToken(serviceToken);
|
||||
};
|
||||
const serviceToken = newServiceToken + "," + privateKey;
|
||||
setServiceToken(serviceToken);
|
||||
};
|
||||
|
||||
function copyToClipboard() {
|
||||
// Get the text field
|
||||
var copyText = document.getElementById("serviceToken");
|
||||
function copyToClipboard() {
|
||||
// Get the text field
|
||||
var copyText = document.getElementById("serviceToken");
|
||||
|
||||
// Select the text field
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999); // For mobile devices
|
||||
// Select the text field
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999); // For mobile devices
|
||||
|
||||
// Copy the text inside the text field
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
// Copy the text inside the text field
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
|
||||
setServiceTokenCopied(true);
|
||||
setTimeout(() => setServiceTokenCopied(false), 2000);
|
||||
// Alert the copied text
|
||||
// alert("Copied the text: " + copyText.value);
|
||||
}
|
||||
setServiceTokenCopied(true);
|
||||
setTimeout(() => setServiceTokenCopied(false), 2000);
|
||||
// Alert the copied text
|
||||
// alert("Copied the text: " + copyText.value);
|
||||
}
|
||||
|
||||
const closeAddServiceTokenModal = () => {
|
||||
closeModal();
|
||||
setServiceTokenName("");
|
||||
setServiceToken("");
|
||||
};
|
||||
const closeAddServiceTokenModal = () => {
|
||||
closeModal();
|
||||
setServiceTokenName("");
|
||||
setServiceToken("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
|
||||
</Transition.Child>
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
{serviceToken == "" ? (
|
||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 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 z-50"
|
||||
>
|
||||
Add a service token for{" "}
|
||||
{workspaceName}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
Specify the name,
|
||||
environment, and expiry
|
||||
period. When a token is
|
||||
generated, you will only be
|
||||
able to see it once before
|
||||
it disappears. Make sure to
|
||||
save it somewhere.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-28 mb-2">
|
||||
<InputField
|
||||
label="Service Token Name"
|
||||
onChangeHandler={
|
||||
setServiceTokenName
|
||||
}
|
||||
type="varName"
|
||||
value={serviceTokenName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-28 mb-2">
|
||||
<ListBox
|
||||
selected={serviceTokenEnv}
|
||||
onChange={setServiceTokenEnv}
|
||||
data={[
|
||||
"Development",
|
||||
"Staging",
|
||||
"Production",
|
||||
"Testing",
|
||||
]}
|
||||
width="full"
|
||||
text="Environment: "
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<ListBox
|
||||
selected={serviceTokenExpiresIn}
|
||||
onChange={
|
||||
setServiceTokenExpiresIn
|
||||
}
|
||||
data={[
|
||||
"1 day",
|
||||
"7 days",
|
||||
"1 month",
|
||||
]}
|
||||
width="full"
|
||||
text="Expires in: "
|
||||
/>
|
||||
</div>
|
||||
<div className="max-w-max">
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
generateServiceToken()
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Add Service Token"
|
||||
textDisabled="Add Service Token"
|
||||
size="md"
|
||||
active={
|
||||
serviceTokenName == ""
|
||||
? false
|
||||
: true
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
) : (
|
||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 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 z-50"
|
||||
>
|
||||
Copy your service token
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
Once you close this popup,
|
||||
you will never see your
|
||||
service token again
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-36">
|
||||
<input
|
||||
type="text"
|
||||
value={serviceToken}
|
||||
id="serviceToken"
|
||||
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
|
||||
></input>
|
||||
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
|
||||
{serviceToken}
|
||||
</div>
|
||||
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||
<button
|
||||
onClick={
|
||||
copyToClipboard
|
||||
}
|
||||
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
|
||||
>
|
||||
{serviceTokenCopied ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="pr-0.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCopy}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
||||
Click to Copy
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
closeAddServiceTokenModal()
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Close"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
)}
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
{serviceToken == "" ? (
|
||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 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 z-50"
|
||||
>
|
||||
Add a service token for {workspaceName}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
Specify the name, environment, and expiry period. When
|
||||
a token is generated, you will only be able to see it
|
||||
once before it disappears. Make sure to save it
|
||||
somewhere.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-28 mb-2">
|
||||
<InputField
|
||||
label="Service Token Name"
|
||||
onChangeHandler={setServiceTokenName}
|
||||
type="varName"
|
||||
value={serviceTokenName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-28 mb-2">
|
||||
<ListBox
|
||||
selected={serviceTokenEnv}
|
||||
onChange={setServiceTokenEnv}
|
||||
data={[
|
||||
"Development",
|
||||
"Staging",
|
||||
"Production",
|
||||
"Testing",
|
||||
]}
|
||||
width="full"
|
||||
text="Environment: "
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<ListBox
|
||||
selected={serviceTokenExpiresIn}
|
||||
onChange={setServiceTokenExpiresIn}
|
||||
data={["1 day", "7 days", "1 month"]}
|
||||
width="full"
|
||||
text="Expires in: "
|
||||
/>
|
||||
</div>
|
||||
<div className="max-w-max">
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={() => generateServiceToken()}
|
||||
color="mineshaft"
|
||||
text="Add Service Token"
|
||||
textDisabled="Add Service Token"
|
||||
size="md"
|
||||
active={serviceTokenName == "" ? false : true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
) : (
|
||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 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 z-50"
|
||||
>
|
||||
Copy your service token
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
Once you close this popup, you will never see your
|
||||
service token again
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-36">
|
||||
<input
|
||||
type="text"
|
||||
value={serviceToken}
|
||||
id="serviceToken"
|
||||
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
|
||||
></input>
|
||||
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
|
||||
{serviceToken}
|
||||
</div>
|
||||
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
|
||||
>
|
||||
{serviceTokenCopied ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="pr-0.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCopy} />
|
||||
)}
|
||||
</button>
|
||||
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
||||
Click to Copy
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={() => closeAddServiceTokenModal()}
|
||||
color="mineshaft"
|
||||
text="Close"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
)}
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddServiceTokenDialog;
|
||||
|
@ -7,107 +7,103 @@ import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
|
||||
const AddUserDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
email,
|
||||
workspaceId,
|
||||
setEmail,
|
||||
currentPlan,
|
||||
orgName
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
email,
|
||||
workspaceId,
|
||||
setEmail,
|
||||
currentPlan,
|
||||
orgName,
|
||||
}) => {
|
||||
const submit = () => {
|
||||
submitModal(email);
|
||||
};
|
||||
const router = useRouter();
|
||||
const submit = () => {
|
||||
submitModal(email);
|
||||
};
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
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.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
Invite others to {orgName}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
An invite is specific to an email address
|
||||
and expires after 1 day. For security reasons,
|
||||
you will need to separately add members to projects.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={setEmail}
|
||||
type="varName"
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
{currentPlan == STRIPE_PRODUCT_STARTER && <div className="flex flex-row">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/billing/" +
|
||||
router.query.id
|
||||
)
|
||||
}
|
||||
>
|
||||
You can add up to 5 members on a Free tier.
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/billing/" +
|
||||
router.query.id
|
||||
)
|
||||
}
|
||||
>
|
||||
Upgrade now.
|
||||
</button>
|
||||
</div>}
|
||||
<div className="mt-4 max-w-max">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text="Invite"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</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">
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
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.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
Invite others to {orgName}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
An invite is specific to an email address and expires
|
||||
after 1 day. For security reasons, you will need to
|
||||
separately add members to projects.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={setEmail}
|
||||
type="varName"
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
{currentPlan == STRIPE_PRODUCT_STARTER && (
|
||||
<div className="flex flex-row">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push("/settings/billing/" + router.query.id)
|
||||
}
|
||||
>
|
||||
You can add up to 5 members on a Free tier.
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push("/settings/billing/" + router.query.id)
|
||||
}
|
||||
>
|
||||
Upgrade now.
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-4 max-w-max">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text="Invite"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</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.Title
|
||||
as="h3"
|
||||
className="text-xl font-medium leading-6 text-gray-300 z-50"
|
||||
@ -139,13 +135,13 @@ const AddUserDialog = ({
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel> */}
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddUserDialog;
|
||||
|
@ -12,94 +12,93 @@ import { Checkbox } from "../table/Checkbox";
|
||||
* @returns
|
||||
*/
|
||||
const AddWorkspaceDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
workspaceName,
|
||||
setWorkspaceName,
|
||||
error,
|
||||
loading,
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
workspaceName,
|
||||
setWorkspaceName,
|
||||
error,
|
||||
loading,
|
||||
}) => {
|
||||
const [addAllUsers, setAddAllUsers] = useState(true);
|
||||
const submit = () => {
|
||||
submitModal(workspaceName, addAllUsers);
|
||||
};
|
||||
const [addAllUsers, setAddAllUsers] = useState(true);
|
||||
const submit = () => {
|
||||
submitModal(workspaceName, addAllUsers);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto z-50">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-bunker-800 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"
|
||||
>
|
||||
Create a new project
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This project will contain your
|
||||
environmental variables.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28 mt-4">
|
||||
<InputField
|
||||
label="Project Name"
|
||||
onChangeHandler={setWorkspaceName}
|
||||
type="varName"
|
||||
value={workspaceName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
error={error.length > 0}
|
||||
errorText={error}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 ml-1">
|
||||
<Checkbox
|
||||
addAllUsers={addAllUsers}
|
||||
setAddAllUsers={setAddAllUsers}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 max-w-min">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
loading={loading}
|
||||
color="mineshaft"
|
||||
text="Create"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
<div className="fixed inset-0 overflow-y-auto z-50">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-bunker-800 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"
|
||||
>
|
||||
Create a new project
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This project will contain your environmental variables.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28 mt-4">
|
||||
<InputField
|
||||
label="Project Name"
|
||||
onChangeHandler={setWorkspaceName}
|
||||
type="varName"
|
||||
value={workspaceName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
error={error.length > 0}
|
||||
errorText={error}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 ml-1">
|
||||
<Checkbox
|
||||
addAllUsers={addAllUsers}
|
||||
setAddAllUsers={setAddAllUsers}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 max-w-min">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
loading={loading}
|
||||
color="mineshaft"
|
||||
text="Create"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddWorkspaceDialog;
|
||||
|
@ -1,78 +1,83 @@
|
||||
import { Fragment, useState } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { Fragment, useState } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import InputField from '../InputField';
|
||||
import InputField from "../InputField";
|
||||
|
||||
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
|
||||
|
||||
const DeleteUserDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
userIdToBeDeleted,
|
||||
}) => {
|
||||
const submit = () => {
|
||||
submitModal(userIdToBeDeleted);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
const DeleteUserDialog = ({isOpen, closeModal, submitModal, userIdToBeDeleted}) => {
|
||||
const submit = () => {
|
||||
submitModal(userIdToBeDeleted);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
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.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
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.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400"
|
||||
>
|
||||
Are you sure you want to remove this user from the
|
||||
workspace?
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This action is irrevertible.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<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"
|
||||
onClick={submit}
|
||||
>
|
||||
Are you sure you want to remove this user from the workspace?
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This action is irrevertible.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<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"
|
||||
onClick={submit}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<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"
|
||||
onClick={submit}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
Delete
|
||||
</button>
|
||||
<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"
|
||||
onClick={submit}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteUserDialog;
|
||||
export default DeleteUserDialog;
|
||||
|
@ -1,338 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faGear,
|
||||
faHouse,
|
||||
faLink,
|
||||
faMobile,
|
||||
faUser,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import getOrganizations from "~/pages/api/organization/getOrgs";
|
||||
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
|
||||
import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers";
|
||||
import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace";
|
||||
import createWorkspace from "~/pages/api/workspace/createWorkspace";
|
||||
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
|
||||
|
||||
import NavBarDashboard from "../navigation/NavBarDashboard";
|
||||
import { decryptAssymmetric, encryptAssymmetric } from "../utilities/cryptography/crypto";
|
||||
import Button from "./buttons/Button";
|
||||
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
|
||||
import Listbox from "./Listbox";
|
||||
|
||||
export default function Layout({ children }) {
|
||||
const router = useRouter();
|
||||
const [workspaceList, setWorkspaceList] = useState([]);
|
||||
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
|
||||
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
|
||||
let [newWorkspaceName, setNewWorkspaceName] = useState("");
|
||||
let [isOpen, setIsOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
function closeModal() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
// TODO: what to do about the fact that 2ids can have the same name
|
||||
|
||||
/**
|
||||
* When a user creates a new workspace, redirect them to the page of the new workspace.
|
||||
* @param {*} workspaceName
|
||||
*/
|
||||
async function submitModal(workspaceName, addAllUsers) {
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 1500);
|
||||
const workspaces = await getWorkspaces();
|
||||
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
|
||||
if (!currentWorkspaces.includes(workspaceName)) {
|
||||
const newWorkspace = await createWorkspace(
|
||||
workspaceName,
|
||||
localStorage.getItem("orgData.id")
|
||||
);
|
||||
let newWorkspaceId;
|
||||
try {
|
||||
newWorkspaceId = newWorkspace._id;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
if (addAllUsers) {
|
||||
let orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
orgUsers.map(async (user) => {
|
||||
if (user.status == "accepted") {
|
||||
let result = await addUserToWorkspace(
|
||||
user.user.email,
|
||||
newWorkspaceId
|
||||
);
|
||||
if (result?.invitee && result?.latestKey) {
|
||||
const PRIVATE_KEY =
|
||||
localStorage.getItem("PRIVATE_KEY");
|
||||
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: result.latestKey.encryptedKey,
|
||||
nonce: result.latestKey.nonce,
|
||||
publicKey: result.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: result.invitee.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
uploadKeys(
|
||||
newWorkspaceId,
|
||||
result.invitee._id,
|
||||
ciphertext,
|
||||
nonce
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
router.push("/dashboard/" + newWorkspaceId + "?Development");
|
||||
setIsOpen(false);
|
||||
setNewWorkspaceName("");
|
||||
} else {
|
||||
setError("A project with this name already exists.");
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
setIsOpen(true);
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
href:
|
||||
"/dashboard/" +
|
||||
workspaceMapping[workspaceSelected] +
|
||||
"?Development",
|
||||
title: "Secrets",
|
||||
emoji: <FontAwesomeIcon icon={faHouse} />,
|
||||
},
|
||||
{
|
||||
href: "/users/" + workspaceMapping[workspaceSelected],
|
||||
title: "Members",
|
||||
emoji: <FontAwesomeIcon icon={faUser} />,
|
||||
},
|
||||
{
|
||||
href: "/integrations/" + workspaceMapping[workspaceSelected],
|
||||
title: "Integrations",
|
||||
emoji: <FontAwesomeIcon icon={faLink} />,
|
||||
},
|
||||
{
|
||||
href: "/settings/project/" + workspaceMapping[workspaceSelected],
|
||||
title: "Project Settings",
|
||||
emoji: <FontAwesomeIcon icon={faGear} />,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(async () => {
|
||||
// Put a user in a workspace if they're not in one yet
|
||||
if (
|
||||
localStorage.getItem("orgData.id") == null ||
|
||||
localStorage.getItem("orgData.id") == ""
|
||||
) {
|
||||
const userOrgs = await getOrganizations();
|
||||
localStorage.setItem("orgData.id", userOrgs[0]._id);
|
||||
}
|
||||
|
||||
let orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
let userWorkspaces = orgUserProjects;
|
||||
if (
|
||||
userWorkspaces.length == 0 &&
|
||||
router.asPath != "/noprojects" &&
|
||||
!router.asPath.includes("settings")
|
||||
) {
|
||||
router.push("/noprojects");
|
||||
} else if (router.asPath != "/noprojects") {
|
||||
const intendedWorkspaceId = router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0];
|
||||
|
||||
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
||||
if (
|
||||
intendedWorkspaceId != "heroku" &&
|
||||
!userWorkspaces
|
||||
.map((workspace) => workspace._id)
|
||||
.includes(intendedWorkspaceId)
|
||||
) {
|
||||
router.push(
|
||||
"/dashboard/" + userWorkspaces[0]._id + "?Development"
|
||||
);
|
||||
} else {
|
||||
setWorkspaceList(
|
||||
userWorkspaces.map((workspace) => workspace.name)
|
||||
);
|
||||
setWorkspaceMapping(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace) => [
|
||||
workspace.name,
|
||||
workspace._id,
|
||||
])
|
||||
)
|
||||
);
|
||||
setWorkspaceSelected(
|
||||
Object.fromEntries(
|
||||
userWorkspaces.map((workspace) => [
|
||||
workspace._id,
|
||||
workspace.name,
|
||||
])
|
||||
)[
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (
|
||||
workspaceMapping[workspaceSelected] &&
|
||||
workspaceMapping[workspaceSelected] !==
|
||||
router.asPath
|
||||
.split("/")
|
||||
[router.asPath.split("/").length - 1].split("?")[0]
|
||||
) {
|
||||
router.push(
|
||||
"/dashboard/" +
|
||||
workspaceMapping[workspaceSelected] +
|
||||
"?Development"
|
||||
);
|
||||
localStorage.setItem(
|
||||
"projectData.id",
|
||||
workspaceMapping[workspaceSelected]
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, [workspaceSelected]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed w-full hidden md:block flex flex-col h-screen">
|
||||
<NavBarDashboard />
|
||||
<div className="flex flex-col md:flex-row flex-1">
|
||||
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
|
||||
<nav>
|
||||
<div className="py-6"></div>
|
||||
<div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4">
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
PROJECT
|
||||
</div>
|
||||
{workspaceList.length > 0 ? (
|
||||
<Listbox
|
||||
selected={workspaceSelected}
|
||||
onChange={setWorkspaceSelected}
|
||||
data={workspaceList}
|
||||
buttonAction={openModal}
|
||||
text=""
|
||||
workspaceMapping={workspaceMapping}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
text="Add Project"
|
||||
onButtonPressed={openModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ul>
|
||||
{workspaceList.length > 0 &&
|
||||
menuItems.map(({ href, title, emoji }) => (
|
||||
<li className="mt-1.5 mx-2" key={title}>
|
||||
{router.asPath.split("/")[1] ===
|
||||
href.split("/")[1] &&
|
||||
([
|
||||
"project",
|
||||
"billing",
|
||||
"org",
|
||||
"personal",
|
||||
].includes(
|
||||
router.asPath.split("/")[2]
|
||||
)
|
||||
? router.asPath.split(
|
||||
"/"
|
||||
)[2] === href.split("/")[2]
|
||||
: true) ? (
|
||||
<div
|
||||
className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`}
|
||||
>
|
||||
<div className="bg-primary w-1 rounded-xl mr-1"></div>
|
||||
<p className="ml-2 mr-4">
|
||||
{emoji}
|
||||
</p>
|
||||
{title}
|
||||
</div>
|
||||
) : router.asPath ==
|
||||
"/noprojects" ? (
|
||||
<div
|
||||
className={`flex p-2 text-white text-sm rounded`}
|
||||
>
|
||||
<p className="ml-2 mr-4">
|
||||
{emoji}
|
||||
</p>
|
||||
{title}
|
||||
</div>
|
||||
) : (
|
||||
<Link href={href}>
|
||||
<div
|
||||
className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`}
|
||||
>
|
||||
<p className="ml-2 mr-4">
|
||||
{emoji}
|
||||
</p>
|
||||
{title}
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
<AddWorkspaceDialog
|
||||
isOpen={isOpen}
|
||||
closeModal={closeModal}
|
||||
submitModal={submitModal}
|
||||
workspaceName={newWorkspaceName}
|
||||
setWorkspaceName={setNewWorkspaceName}
|
||||
error={error}
|
||||
loading={loading}
|
||||
/>
|
||||
<main className="flex-1 bg-bunker-800">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
<div className="block md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faMobile}
|
||||
className="text-gray-300 text-7xl mb-8"
|
||||
/>
|
||||
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
|
||||
{" "}
|
||||
To use Infisical, please log in through a device with larger
|
||||
dimensions.{" "}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -2,50 +2,63 @@ import React from "react";
|
||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
|
||||
/**
|
||||
* This is the notification that pops up at the bottom right when a user performs a certain action
|
||||
* @param {object} org
|
||||
* @param {string} org.buttonText - text of the button inside the notification
|
||||
* @param {string} org.buttonLink - where the button leads to
|
||||
* @param {string} org.titleText - the text at the top of a notification
|
||||
* @param {string} org.emoji - the emoji in the notification title
|
||||
* @param {string} org.textLine1 - first line of the text in the notification
|
||||
* @param {string} org.textLine2 - second line of the text in the notification
|
||||
* @param {string} org.setCheckDocsPopUpVisible - the functions that closes the popup
|
||||
* @returns
|
||||
*/
|
||||
export default function BottonRightPopup({
|
||||
buttonText,
|
||||
buttonLink,
|
||||
titleText,
|
||||
emoji,
|
||||
textLine1,
|
||||
textLine2,
|
||||
setCheckDocsPopUpVisible,
|
||||
buttonText,
|
||||
buttonLink,
|
||||
titleText,
|
||||
emoji,
|
||||
textLine1,
|
||||
textLine2,
|
||||
setCheckDocsPopUpVisible,
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
class="z-50 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-xl absolute bottom-0 right-0 mr-6 mb-6"
|
||||
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="font-bold text-xl mr-2 mt-0.5 flex flex-row">
|
||||
<div>{titleText}</div>
|
||||
<div class="ml-2.5">{emoji}</div>
|
||||
</div>
|
||||
<button
|
||||
className="mt-1"
|
||||
onClick={() => setCheckDocsPopUpVisible(false)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faXmark}
|
||||
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">
|
||||
{textLine1}
|
||||
</div>
|
||||
<div class="block sm:inline mb-4 px-6">{textLine2}</div>
|
||||
<div className="flex flex-row px-6 w-full">
|
||||
{/*eslint-disable-next-line react/jsx-no-target-blank */}
|
||||
<a
|
||||
class="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center"
|
||||
href={buttonLink}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
{buttonText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
class="z-50 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-xl absolute bottom-0 right-0 mr-6 mb-6"
|
||||
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="font-bold text-xl mr-2 mt-0.5 flex flex-row">
|
||||
<div>{titleText}</div>
|
||||
<div class="ml-2.5">{emoji}</div>
|
||||
</div>
|
||||
<button
|
||||
className="mt-1"
|
||||
onClick={() => setCheckDocsPopUpVisible(false)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faXmark}
|
||||
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">
|
||||
{textLine1}
|
||||
</div>
|
||||
<div class="block sm:inline mb-4 px-6">{textLine2}</div>
|
||||
<div className="flex flex-row px-6 w-full">
|
||||
{/*eslint-disable-next-line react/jsx-no-target-blank */}
|
||||
<a
|
||||
class="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center"
|
||||
href={buttonLink}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
{buttonText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,28 +1,28 @@
|
||||
import React from "react";
|
||||
|
||||
export const Checkbox = ({ addAllUsers, setAddAllUsers }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row items-center">
|
||||
{addAllUsers == true ? (
|
||||
<input
|
||||
type="checkbox"
|
||||
className="accent-primary h-4 w-4"
|
||||
checked
|
||||
readOnly
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm"
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
></div>
|
||||
)}
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row items-center">
|
||||
{addAllUsers == true ? (
|
||||
<input
|
||||
type="checkbox"
|
||||
className="accent-primary h-4 w-4"
|
||||
checked
|
||||
readOnly
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm"
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
></div>
|
||||
)}
|
||||
|
||||
<label className="ml-2 text-gray-500 text-sm">
|
||||
Add all members of my organization to this project.
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
<label className="ml-2 text-gray-500 text-sm">
|
||||
Add all members of my organization to this project.
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -2,81 +2,76 @@ import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { faX } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { reverseEnvMapping } from "../../../public/data/frequentConstants";
|
||||
import guidGenerator from "../../utilities/randomId";
|
||||
import Button from "../buttons/Button";
|
||||
|
||||
const roles = ["admin", "user"];
|
||||
|
||||
const reverseEnvMapping = {
|
||||
"dev": "Development",
|
||||
"staging": "Staging",
|
||||
"prod": "Production",
|
||||
"test": "Testing"
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
|
||||
* #TODO: add the possibility of choosing and doing operations on multiple users.
|
||||
* @param {*} props
|
||||
* @returns
|
||||
*/
|
||||
const ServiceTokenTable = ({
|
||||
data,
|
||||
workspaceName
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const ServiceTokenTable = ({ data, workspaceName }) => {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
|
||||
<table className="w-full my-1">
|
||||
<thead className="text-bunker-300">
|
||||
<tr>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Token name</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Project</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Environment</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Valid until</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data?.length > 0
|
||||
? data
|
||||
.map((row, index) => {
|
||||
return (
|
||||
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-800/5 duration-100">
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.name}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{workspaceName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{reverseEnvMapping[row.environment]}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{new Date(row.expiresAt).toUTCString()}
|
||||
</td>
|
||||
<td className="py-2 border-mineshaft-700 border-t">
|
||||
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
||||
<Button
|
||||
onButtonPressed={() => {}}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: <tr>
|
||||
<td colSpan="4" className="text-center pt-7 pb-4 text-bunker-400">No service tokens yet</td>
|
||||
</tr>}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
|
||||
<table className="w-full my-1">
|
||||
<thead className="text-bunker-300">
|
||||
<tr>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Token name</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Project</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Environment</th>
|
||||
<th className="text-left pl-6 pt-2.5 pb-2">Valid until</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data?.length > 0 ? (
|
||||
data.map((row, index) => {
|
||||
return (
|
||||
<tr
|
||||
key={guidGenerator()}
|
||||
className="bg-bunker-800 hover:bg-bunker-800/5 duration-100"
|
||||
>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.name}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{workspaceName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{reverseEnvMapping[row.environment]}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{new Date(row.expiresAt).toUTCString()}
|
||||
</td>
|
||||
<td className="py-2 border-mineshaft-700 border-t">
|
||||
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
|
||||
<Button
|
||||
onButtonPressed={() => {}}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="4" className="text-center pt-7 pb-4 text-bunker-400">
|
||||
No service tokens yet
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceTokenTable;
|
||||
|
@ -13,8 +13,8 @@ import Button from "../buttons/Button";
|
||||
import Listbox from "../Listbox";
|
||||
|
||||
const {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
} = require("../../utilities/cryptography/crypto");
|
||||
const nacl = require("tweetnacl");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
@ -28,245 +28,214 @@ const roles = ["admin", "user"];
|
||||
* @returns
|
||||
*/
|
||||
const UserTable = ({
|
||||
userData,
|
||||
changeData,
|
||||
myUser,
|
||||
filter,
|
||||
resendInvite,
|
||||
isOrg,
|
||||
onClick,
|
||||
deleteUser,
|
||||
setUserIdToBeDeleted,
|
||||
userData,
|
||||
changeData,
|
||||
myUser,
|
||||
filter,
|
||||
resendInvite,
|
||||
isOrg,
|
||||
onClick,
|
||||
deleteUser,
|
||||
setUserIdToBeDeleted,
|
||||
}) => {
|
||||
const [roleSelected, setRoleSelected] = useState(
|
||||
Array(userData?.length).fill(userData.map((user) => user.role))
|
||||
);
|
||||
const router = useRouter();
|
||||
const [myRole, setMyRole] = useState("member");
|
||||
const [roleSelected, setRoleSelected] = useState(
|
||||
Array(userData?.length).fill(userData.map((user) => user.role))
|
||||
);
|
||||
const router = useRouter();
|
||||
const [myRole, setMyRole] = useState("member");
|
||||
|
||||
// Delete the row in the table (e.g. a user)
|
||||
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
|
||||
const handleDelete = (membershipId, index, e) => {
|
||||
// setUserIdToBeDeleted(userId);
|
||||
// onClick();
|
||||
if (isOrg) {
|
||||
deleteUserFromOrganization(membershipId);
|
||||
} else {
|
||||
deleteUserFromWorkspace(membershipId);
|
||||
}
|
||||
changeData(userData.filter((v, i) => i !== index));
|
||||
setRoleSelected([
|
||||
...roleSelected.slice(0, index),
|
||||
...roleSelected.slice(index + 1, userData?.length),
|
||||
]);
|
||||
};
|
||||
// Delete the row in the table (e.g. a user)
|
||||
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
|
||||
const handleDelete = (membershipId, index, e) => {
|
||||
// setUserIdToBeDeleted(userId);
|
||||
// onClick();
|
||||
if (isOrg) {
|
||||
deleteUserFromOrganization(membershipId);
|
||||
} else {
|
||||
deleteUserFromWorkspace(membershipId);
|
||||
}
|
||||
changeData(userData.filter((v, i) => i !== index));
|
||||
setRoleSelected([
|
||||
...roleSelected.slice(0, index),
|
||||
...roleSelected.slice(index + 1, userData?.length),
|
||||
]);
|
||||
};
|
||||
|
||||
// Update the rold of a certain user
|
||||
const handleRoleUpdate = (index, e) => {
|
||||
changeUserRoleInWorkspace(userData[index].membershipId, e);
|
||||
changeData([
|
||||
...userData.slice(0, index),
|
||||
...[
|
||||
{
|
||||
key: userData[index].key,
|
||||
firstName: userData[index].firstName,
|
||||
lastName: userData[index].lastName,
|
||||
email: userData[index].email,
|
||||
role: e,
|
||||
status: userData[index].status,
|
||||
userId: userData[index].userId,
|
||||
membershipId: userData[index].membershipId,
|
||||
publicKey: userData[index].publicKey,
|
||||
},
|
||||
],
|
||||
...userData.slice(index + 1, userData?.length),
|
||||
]);
|
||||
};
|
||||
// Update the rold of a certain user
|
||||
const handleRoleUpdate = (index, e) => {
|
||||
changeUserRoleInWorkspace(userData[index].membershipId, e);
|
||||
changeData([
|
||||
...userData.slice(0, index),
|
||||
...[
|
||||
{
|
||||
key: userData[index].key,
|
||||
firstName: userData[index].firstName,
|
||||
lastName: userData[index].lastName,
|
||||
email: userData[index].email,
|
||||
role: e,
|
||||
status: userData[index].status,
|
||||
userId: userData[index].userId,
|
||||
membershipId: userData[index].membershipId,
|
||||
publicKey: userData[index].publicKey,
|
||||
},
|
||||
],
|
||||
...userData.slice(index + 1, userData?.length),
|
||||
]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setMyRole(userData.filter((user) => user.email == myUser)[0]?.role);
|
||||
}, [userData, myUser]);
|
||||
useEffect(() => {
|
||||
setMyRole(userData.filter((user) => user.email == myUser)[0]?.role);
|
||||
}, [userData, myUser]);
|
||||
|
||||
const grantAccess = async (id, publicKey) => {
|
||||
let result = await getLatestFileKey(router.query.id);
|
||||
const grantAccess = async (id, publicKey) => {
|
||||
let result = await getLatestFileKey({workspaceId: router.query.id});
|
||||
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: result.latestKey.encryptedKey,
|
||||
nonce: result.latestKey.nonce,
|
||||
publicKey: result.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: result.latestKey.encryptedKey,
|
||||
nonce: result.latestKey.nonce,
|
||||
publicKey: result.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
uploadKeys(router.query.id, id, ciphertext, nonce);
|
||||
router.reload();
|
||||
};
|
||||
uploadKeys(router.query.id, id, ciphertext, nonce);
|
||||
router.reload();
|
||||
};
|
||||
|
||||
const deleteMembershipAndResendInvite = (email, membershipId) => {
|
||||
// deleteUserFromWorkspace(membershipId);
|
||||
resendInvite(email);
|
||||
};
|
||||
const deleteMembershipAndResendInvite = (email, membershipId) => {
|
||||
// deleteUserFromWorkspace(membershipId);
|
||||
resendInvite(email);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||
<div className="absolute rounded-t-md w-full h-14 bg-white/5"></div>
|
||||
<table className="w-full my-1">
|
||||
<thead className="text-gray-400">
|
||||
<tr>
|
||||
<th className="text-left pl-6 py-3.5">First Name</th>
|
||||
<th className="text-left pl-6 py-3.5">Last Name</th>
|
||||
<th className="text-left pl-6 py-3.5">Email</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userData?.filter(
|
||||
(user) =>
|
||||
user.firstName?.toLowerCase().includes(filter) ||
|
||||
user.lastName?.toLowerCase().includes(filter) ||
|
||||
user.email?.toLowerCase().includes(filter)
|
||||
).length > 0 &&
|
||||
userData
|
||||
?.filter(
|
||||
(user) =>
|
||||
user.firstName
|
||||
?.toLowerCase()
|
||||
.includes(filter) ||
|
||||
user.lastName
|
||||
?.toLowerCase()
|
||||
.includes(filter) ||
|
||||
user.email?.toLowerCase().includes(filter)
|
||||
)
|
||||
.map((row, index) => {
|
||||
return (
|
||||
<tr
|
||||
key={guidGenerator()}
|
||||
className="bg-bunker-800 hover:bg-bunker-800/5"
|
||||
>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.firstName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.lastName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.email}
|
||||
</td>
|
||||
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
|
||||
<div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center">
|
||||
{row.status == "granted" &&
|
||||
((myRole == "admin" &&
|
||||
row.role != "owner") ||
|
||||
myRole == "owner") &&
|
||||
myUser !== row.email ? (
|
||||
<Listbox
|
||||
selected={row.role}
|
||||
onChange={(e) =>
|
||||
handleRoleUpdate(
|
||||
index,
|
||||
e
|
||||
)
|
||||
}
|
||||
data={
|
||||
myRole == "owner"
|
||||
? [
|
||||
"owner",
|
||||
"admin",
|
||||
"member",
|
||||
]
|
||||
: [
|
||||
"admin",
|
||||
"member",
|
||||
]
|
||||
}
|
||||
text="Role: "
|
||||
membershipId={
|
||||
row.membershipId
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
row.status != "invited" &&
|
||||
row.status !=
|
||||
"verified" && (
|
||||
<Listbox
|
||||
selected={row.role}
|
||||
text="Role: "
|
||||
membershipId={
|
||||
row.membershipId
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{(row.status == "invited" ||
|
||||
row.status ==
|
||||
"verified") && (
|
||||
<div className="w-full pl-12">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
deleteMembershipAndResendInvite(
|
||||
row.email,
|
||||
row.membershipId
|
||||
)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Resend Invite"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{row.status == "completed" &&
|
||||
myUser !== row.email && (
|
||||
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
grantAccess(
|
||||
row.userId,
|
||||
row.publicKey
|
||||
)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Grant Access"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{myUser !== row.email &&
|
||||
// row.role != "admin" &&
|
||||
myRole != "member" ? (
|
||||
<div className="opacity-50 hover:opacity-100 flex items-center">
|
||||
<Button
|
||||
onButtonPressed={(e) =>
|
||||
handleDelete(
|
||||
row.membershipId,
|
||||
index,
|
||||
e
|
||||
)
|
||||
}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-9 h-9"></div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
|
||||
<div className="absolute rounded-t-md w-full h-14 bg-white/5"></div>
|
||||
<table className="w-full my-1">
|
||||
<thead className="text-gray-400">
|
||||
<tr>
|
||||
<th className="text-left pl-6 py-3.5">First Name</th>
|
||||
<th className="text-left pl-6 py-3.5">Last Name</th>
|
||||
<th className="text-left pl-6 py-3.5">Email</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userData?.filter(
|
||||
(user) =>
|
||||
user.firstName?.toLowerCase().includes(filter) ||
|
||||
user.lastName?.toLowerCase().includes(filter) ||
|
||||
user.email?.toLowerCase().includes(filter)
|
||||
).length > 0 &&
|
||||
userData
|
||||
?.filter(
|
||||
(user) =>
|
||||
user.firstName?.toLowerCase().includes(filter) ||
|
||||
user.lastName?.toLowerCase().includes(filter) ||
|
||||
user.email?.toLowerCase().includes(filter)
|
||||
)
|
||||
.map((row, index) => {
|
||||
return (
|
||||
<tr
|
||||
key={guidGenerator()}
|
||||
className="bg-bunker-800 hover:bg-bunker-800/5"
|
||||
>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.firstName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.lastName}
|
||||
</td>
|
||||
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
|
||||
{row.email}
|
||||
</td>
|
||||
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
|
||||
<div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center">
|
||||
{row.status == "granted" &&
|
||||
((myRole == "admin" && row.role != "owner") ||
|
||||
myRole == "owner") &&
|
||||
myUser !== row.email ? (
|
||||
<Listbox
|
||||
selected={row.role}
|
||||
onChange={(e) => handleRoleUpdate(index, e)}
|
||||
data={
|
||||
myRole == "owner"
|
||||
? ["owner", "admin", "member"]
|
||||
: ["admin", "member"]
|
||||
}
|
||||
text="Role: "
|
||||
membershipId={row.membershipId}
|
||||
/>
|
||||
) : (
|
||||
row.status != "invited" &&
|
||||
row.status != "verified" && (
|
||||
<Listbox
|
||||
selected={row.role}
|
||||
text="Role: "
|
||||
membershipId={row.membershipId}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{(row.status == "invited" ||
|
||||
row.status == "verified") && (
|
||||
<div className="w-full pl-9">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
deleteMembershipAndResendInvite(
|
||||
row.email,
|
||||
row.membershipId
|
||||
)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Resend Invite"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{row.status == "completed" && myUser !== row.email && (
|
||||
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
grantAccess(row.userId, row.publicKey)
|
||||
}
|
||||
color="mineshaft"
|
||||
text="Grant Access"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{myUser !== row.email &&
|
||||
// row.role != "admin" &&
|
||||
myRole != "member" ? (
|
||||
<div className="opacity-50 hover:opacity-100 flex items-center">
|
||||
<Button
|
||||
onButtonPressed={(e) =>
|
||||
handleDelete(row.membershipId, index, e)
|
||||
}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-9 h-9"></div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTable;
|
||||
|
@ -3,91 +3,82 @@ import React from "react";
|
||||
import StripeRedirect from "~/pages/api/organization/StripeRedirect";
|
||||
|
||||
export default function Plan({ plan }) {
|
||||
return (
|
||||
<div
|
||||
className={`relative flex flex-col justify-between border border-2 min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${
|
||||
(plan.name != "Starter") & (plan.current == true)
|
||||
? "border-primary"
|
||||
: "border-chicago-700"
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={`relative flex flex-col justify-between border border-2 min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${
|
||||
(plan.name != "Starter") & (plan.current == true)
|
||||
? "border-primary"
|
||||
: "border-chicago-700"
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row justify-between items-center relative z-10">
|
||||
<p
|
||||
className={`px-6 py-4 text-3xl font-semibold text-gray-400`}
|
||||
>
|
||||
{plan.name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flwx-row items-end justify-start mb-4">
|
||||
<p className="pl-6 text-3xl font-semibold text-primary">
|
||||
{plan.price}
|
||||
</p>
|
||||
<p className="pl-3 mb-1 text-lg text-gray-400">
|
||||
{plan.priceExplanation}
|
||||
</p>
|
||||
</div>
|
||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||
{plan.text}
|
||||
</p>
|
||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||
{plan.subtext}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
{plan.current == false ? (
|
||||
<>
|
||||
{plan.buttonTextMain == "Schedule a Demo" ? (
|
||||
<a
|
||||
href="/scheduledemo"
|
||||
target='_blank rel="noopener"'
|
||||
>
|
||||
<div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max">
|
||||
{plan.buttonTextMain}
|
||||
</div>
|
||||
</a>
|
||||
) : (
|
||||
<div
|
||||
className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 text-gray-400 font-semibold ${
|
||||
plan.buttonTextMain == "Downgrade"
|
||||
? "hover:bg-red hover:text-white hover:border-red"
|
||||
: "hover:bg-primary hover:text-black hover:border-primary"
|
||||
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
|
||||
>
|
||||
<button
|
||||
onClick={() =>
|
||||
StripeRedirect({
|
||||
orgId: localStorage.getItem(
|
||||
"orgData.id"
|
||||
),
|
||||
})
|
||||
}
|
||||
>
|
||||
{plan.buttonTextMain}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<a href="/pricing" target='_blank rel="noopener"'>
|
||||
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
|
||||
{plan.buttonTextSecondary}
|
||||
</div>
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
|
||||
(plan.name != "Starter") & (plan.current == true)
|
||||
? "bg-primary"
|
||||
: "bg-chicago-700"
|
||||
}`}
|
||||
>
|
||||
<p className="text-xs text-black font-semibold">
|
||||
CURRENT PLAN
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row justify-between items-center relative z-10">
|
||||
<p className={`px-6 py-4 text-3xl font-semibold text-gray-400`}>
|
||||
{plan.name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flwx-row items-end justify-start mb-4">
|
||||
<p className="pl-6 text-3xl font-semibold text-primary">
|
||||
{plan.price}
|
||||
</p>
|
||||
<p className="pl-3 mb-1 text-lg text-gray-400">
|
||||
{plan.priceExplanation}
|
||||
</p>
|
||||
</div>
|
||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||
{plan.text}
|
||||
</p>
|
||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||
{plan.subtext}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
{plan.current == false ? (
|
||||
<>
|
||||
{plan.buttonTextMain == "Schedule a Demo" ? (
|
||||
<a href="/scheduledemo" target='_blank rel="noopener"'>
|
||||
<div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max">
|
||||
{plan.buttonTextMain}
|
||||
</div>
|
||||
</a>
|
||||
) : (
|
||||
<div
|
||||
className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 text-gray-400 font-semibold ${
|
||||
plan.buttonTextMain == "Downgrade"
|
||||
? "hover:bg-red hover:text-white hover:border-red"
|
||||
: "hover:bg-primary hover:text-black hover:border-primary"
|
||||
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
|
||||
>
|
||||
<button
|
||||
onClick={() =>
|
||||
StripeRedirect({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
})
|
||||
}
|
||||
>
|
||||
{plan.buttonTextMain}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<a href="https://infisical.com/pricing" target='_blank rel="noopener"'>
|
||||
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
|
||||
{plan.buttonTextSecondary}
|
||||
</div>
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
|
||||
(plan.name != "Starter") & (plan.current == true)
|
||||
? "bg-primary"
|
||||
: "bg-chicago-700"
|
||||
}`}
|
||||
>
|
||||
<p className="text-xs text-black font-semibold">CURRENT PLAN</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
61
frontend/components/context/Notifications/Notification.tsx
Normal file
61
frontend/components/context/Notifications/Notification.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { faXmarkCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import classnames from "classnames";
|
||||
|
||||
import { Notification as NotificationType } from "./NotificationProvider";
|
||||
|
||||
interface NotificationProps {
|
||||
notification: Required<NotificationType>;
|
||||
clearNotification: (text: string) => void;
|
||||
}
|
||||
|
||||
const Notification = ({
|
||||
notification,
|
||||
clearNotification,
|
||||
}: NotificationProps) => {
|
||||
const timeout = useRef<number>();
|
||||
|
||||
const handleClearNotification = () => clearNotification(notification.text);
|
||||
|
||||
const setNotifTimeout = () => {
|
||||
timeout.current = window.setTimeout(
|
||||
handleClearNotification,
|
||||
notification.timeoutMs
|
||||
);
|
||||
};
|
||||
|
||||
const cancelNotifTimeout = () => {
|
||||
clearTimeout(timeout.current);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setNotifTimeout();
|
||||
|
||||
return cancelNotifTimeout;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
"w-full flex items-center justify-between px-4 py-3 rounded pointer-events-auto",
|
||||
{
|
||||
"bg-green-600": notification.type === "success",
|
||||
"bg-red-500": notification.type === "error",
|
||||
"bg-blue-500": notification.type === "info",
|
||||
}
|
||||
)}
|
||||
role="alert"
|
||||
>
|
||||
<p className="text-white text-sm font-bold">{notification.text}</p>
|
||||
<button
|
||||
className="bg-white/5 rounded-lg p-3"
|
||||
onClick={() => clearNotification(notification.text)}
|
||||
>
|
||||
<FontAwesomeIcon className="text-white" icon={faXmarkCircle} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notification;
|
@ -0,0 +1,69 @@
|
||||
import { createContext, ReactNode, useContext, useState } from "react";
|
||||
|
||||
import Notifications from "./Notifications";
|
||||
|
||||
type NotificationType = "success" | "error" | "info";
|
||||
|
||||
export type Notification = {
|
||||
text: string;
|
||||
type?: NotificationType;
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
type NotificationContextState = {
|
||||
createNotification: (newNotification: Notification) => void;
|
||||
};
|
||||
|
||||
const NotificationContext = createContext<NotificationContextState>({
|
||||
createNotification: () => console.log("createNotification not set!"),
|
||||
});
|
||||
|
||||
export const useNotificationContext = () => useContext(NotificationContext);
|
||||
|
||||
interface NotificationProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const NotificationProvider = ({ children }: NotificationProviderProps) => {
|
||||
const [notifications, setNotifications] = useState<Required<Notification>[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const clearNotification = (text: string) => {
|
||||
return setNotifications((state) =>
|
||||
state.filter((notif) => notif.text !== text)
|
||||
);
|
||||
};
|
||||
|
||||
const createNotification = ({
|
||||
text,
|
||||
type = "success",
|
||||
timeoutMs = 2000,
|
||||
}: Notification) => {
|
||||
const doesNotifExist = notifications.some((notif) => notif.text === text);
|
||||
|
||||
if (doesNotifExist) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newNotification: Required<Notification> = { text, type, timeoutMs };
|
||||
|
||||
return setNotifications((state) => [...state, newNotification]);
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider
|
||||
value={{
|
||||
createNotification,
|
||||
}}
|
||||
>
|
||||
<Notifications
|
||||
notifications={notifications}
|
||||
clearNotification={clearNotification}
|
||||
/>
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationProvider;
|
30
frontend/components/context/Notifications/Notifications.tsx
Normal file
30
frontend/components/context/Notifications/Notifications.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import Notification from "./Notification";
|
||||
import { Notification as NotificationType } from "./NotificationProvider";
|
||||
|
||||
interface NoticationsProps {
|
||||
notifications: Required<NotificationType>[];
|
||||
clearNotification: (text: string) => void;
|
||||
}
|
||||
|
||||
const Notifications = ({
|
||||
notifications,
|
||||
clearNotification,
|
||||
}: NoticationsProps) => {
|
||||
if (!notifications.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="hidden fixed z-50 md:flex md:flex-col-reverse bottom-1 gap-y-2 w-96 h-full right-1 pointer-events-none">
|
||||
{notifications.map((notif) => (
|
||||
<Notification
|
||||
key={notif.text}
|
||||
notification={notif}
|
||||
clearNotification={clearNotification}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notifications;
|
@ -1,143 +0,0 @@
|
||||
import React, { Fragment } from "react";
|
||||
import { faCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import guidGenerator from "../utilities/randomId";
|
||||
|
||||
/**
|
||||
* This function splits the input of a dashboard field into the parts that are inside and outside of ${...}
|
||||
* @param {string} text - the value of the input in the Dashboard Input Field
|
||||
* @returns
|
||||
*/
|
||||
const findReferences = (text) => {
|
||||
var splitText = text.split("${");
|
||||
let textArray = [splitText[0]];
|
||||
for (var i = 1; i < splitText.length; i++) {
|
||||
let insideBrackets = "${" + splitText[i].split("}")[0];
|
||||
if (splitText[i].includes("}")) {
|
||||
insideBrackets += "}";
|
||||
}
|
||||
textArray.push(insideBrackets);
|
||||
textArray.push(splitText[i].split("}")[1]);
|
||||
}
|
||||
return textArray;
|
||||
};
|
||||
|
||||
/**
|
||||
* This component renders the input fields on the dashboard
|
||||
* @param {object} obj - the order number of a keyPair
|
||||
* @param {number} obj.index - the order number of a keyPair
|
||||
* @param {function} obj.onChangeHandler - what happens when the input is modified
|
||||
* @param {string} obj.type - whether the input field is for a Key Name or for a Key Value
|
||||
* @param {string} obj.value - value of the InputField
|
||||
* @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard
|
||||
* @returns
|
||||
*/
|
||||
const DashboardInputField = ({
|
||||
index,
|
||||
onChangeHandler,
|
||||
type,
|
||||
value,
|
||||
blurred,
|
||||
}) => {
|
||||
if (type === "varName") {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => onChangeHandler(e.target.value, index)}
|
||||
type={type}
|
||||
value={value}
|
||||
className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200"
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (type === "value") {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => onChangeHandler(e.target.value, index)}
|
||||
type={type}
|
||||
value={value}
|
||||
className={`${
|
||||
blurred
|
||||
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
|
||||
: ""
|
||||
} asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`}
|
||||
spellCheck="false"
|
||||
/>
|
||||
<div
|
||||
className={`${
|
||||
blurred
|
||||
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
|
||||
: ""
|
||||
} flex flex-row font-mono absolute z-0 ph-no-capture bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-100`}
|
||||
>
|
||||
{findReferences(value).map((texts, id) => {
|
||||
if (id % 2 == 0 || texts.length <= 2) {
|
||||
return (
|
||||
<span className="ph-no-capture" key={id}>
|
||||
{texts}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span
|
||||
className="ph-no-capture text-yellow"
|
||||
key={id}
|
||||
>
|
||||
{texts.slice(0, 2)}
|
||||
<span className="ph-no-capture text-yellow-200/80">
|
||||
{texts.slice(2, texts.length - 1)}
|
||||
</span>
|
||||
{texts.slice(
|
||||
texts.length - 1,
|
||||
texts.length
|
||||
) == "}" ? (
|
||||
<span className="ph-no-capture text-yellow">
|
||||
{texts.slice(
|
||||
texts.length - 1,
|
||||
texts.length
|
||||
)}{" "}
|
||||
</span>
|
||||
) : (
|
||||
<span className="ph-no-capture text-yellow-400">
|
||||
{texts.slice(
|
||||
texts.length - 1,
|
||||
texts.length
|
||||
)}{" "}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{blurred && (
|
||||
<div className="z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-9 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
|
||||
<p className="ml-2"></p>
|
||||
{value
|
||||
.split("")
|
||||
.slice(0, 42)
|
||||
.map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default React.memo(DashboardInputField);
|
156
frontend/components/dashboard/DashboardInputField.tsx
Normal file
156
frontend/components/dashboard/DashboardInputField.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
import React, { SyntheticEvent, useRef } from "react";
|
||||
import { faCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import guidGenerator from "../utilities/randomId";
|
||||
|
||||
const REGEX = /([$]{.*?})/g;
|
||||
|
||||
interface DashboardInputFieldProps {
|
||||
index: number;
|
||||
onChangeHandler: (value: string, index: number) => void;
|
||||
value: string;
|
||||
type: "varName" | "value";
|
||||
blurred: boolean;
|
||||
duplicates: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This component renders the input fields on the dashboard
|
||||
* @param {object} obj - the order number of a keyPair
|
||||
* @param {number} obj.index - the order number of a keyPair
|
||||
* @param {function} obj.onChangeHandler - what happens when the input is modified
|
||||
* @param {string} obj.type - whether the input field is for a Key Name or for a Key Value
|
||||
* @param {string} obj.value - value of the InputField
|
||||
* @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard
|
||||
* @param {string[]} obj.duplicates - list of all the duplicated key names on the dashboard
|
||||
* @returns
|
||||
*/
|
||||
|
||||
const DashboardInputField = ({
|
||||
index,
|
||||
onChangeHandler,
|
||||
type,
|
||||
value,
|
||||
blurred,
|
||||
duplicates,
|
||||
}: DashboardInputFieldProps) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
|
||||
if (ref.current == null) return;
|
||||
|
||||
ref.current.scrollTop = e.currentTarget.scrollTop;
|
||||
ref.current.scrollLeft = e.currentTarget.scrollLeft;
|
||||
};
|
||||
|
||||
if (type === "varName") {
|
||||
const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != "";
|
||||
const hasDuplicates = duplicates?.includes(value);
|
||||
const error = startsWithNumber || hasDuplicates;
|
||||
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
|
||||
error ? "border-red" : "border-mineshaft-500"
|
||||
} rounded-md`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) =>
|
||||
onChangeHandler(e.target.value.toUpperCase(), index)
|
||||
}
|
||||
type={type}
|
||||
value={value}
|
||||
className={`z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 ${
|
||||
error ? "focus:ring-red/50" : "focus:ring-primary/50"
|
||||
} duration-200`}
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
{startsWithNumber && (
|
||||
<p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs">
|
||||
Should not start with a number
|
||||
</p>
|
||||
)}
|
||||
{hasDuplicates && !startsWithNumber && (
|
||||
<p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs">
|
||||
Secret names should be unique
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else if (type === "value") {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div
|
||||
className={`group relative whitespace-pre flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
|
||||
>
|
||||
<input
|
||||
value={value}
|
||||
onChange={(e) => onChangeHandler(e.target.value, index)}
|
||||
onScroll={syncScroll}
|
||||
className={`${
|
||||
blurred
|
||||
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
|
||||
: ""
|
||||
} z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
|
||||
spellCheck="false"
|
||||
/>
|
||||
<div
|
||||
ref={ref}
|
||||
className={`${
|
||||
blurred
|
||||
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
|
||||
: ""
|
||||
} absolute flex flex-row whitespace-pre font-mono z-0 ph-no-capture max-w-2xl overflow-x-scroll bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
|
||||
>
|
||||
{value.split(REGEX).map((word, id) => {
|
||||
if (word.match(REGEX) !== null) {
|
||||
return (
|
||||
<span className="ph-no-capture text-yellow" key={id}>
|
||||
{word.slice(0, 2)}
|
||||
<span className="ph-no-capture text-yellow-200/80">
|
||||
{word.slice(2, word.length - 1)}
|
||||
</span>
|
||||
{word.slice(word.length - 1, word.length) == "}" ? (
|
||||
<span className="ph-no-capture text-yellow">
|
||||
{word.slice(word.length - 1, word.length)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="ph-no-capture text-yellow-400">
|
||||
{word.slice(word.length - 1, word.length)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span key={id} className="ph-no-capture">
|
||||
{word}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
{blurred && (
|
||||
<div className="absolute flex flex-row items-center z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible h-9 w-full max-w-2xl rounded-md text-gray-400/50 text-clip">
|
||||
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||
{value.split("").map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>Something Wrong</>;
|
||||
};
|
||||
|
||||
export default React.memo(DashboardInputField);
|
@ -1,186 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import Button from "../basic/buttons/Button";
|
||||
import Error from "../basic/Error";
|
||||
import parse from "../utilities/file";
|
||||
import guidGenerator from "../utilities/randomId";
|
||||
|
||||
const DropZone = ({
|
||||
setData,
|
||||
setErrorDragAndDrop,
|
||||
createNewFile,
|
||||
errorDragAndDrop,
|
||||
addPresetRow,
|
||||
setButtonReady,
|
||||
keysExist,
|
||||
numCurrentRows
|
||||
}) => {
|
||||
const handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleDragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// set dropEffect to copy i.e copy of the source item
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
};
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// This function function immediately parses the file after it is dropped
|
||||
const handleDrop = async (e) => {
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 5000);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
|
||||
var file = e.dataTransfer.files[0],
|
||||
reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
const keyPairs = parse(event.target.result)
|
||||
const newData = Object.keys(keyPairs)
|
||||
.map((key, index) => [
|
||||
guidGenerator(),
|
||||
numCurrentRows + index,
|
||||
key,
|
||||
keyPairs[key],
|
||||
"shared",
|
||||
]);
|
||||
setData(newData);
|
||||
setButtonReady(true);
|
||||
};
|
||||
|
||||
// If something is wrong show an error
|
||||
try {
|
||||
reader.readAsText(file);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setErrorDragAndDrop(true);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop)
|
||||
const handleFileSelect = (e) => {
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 5000);
|
||||
var file = e.target.files[0],
|
||||
reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
const newData = event.target.result
|
||||
.split("\n")
|
||||
.map((line, index) => [
|
||||
guidGenerator(),
|
||||
numCurrentRows + index,
|
||||
line.split("=")[0],
|
||||
line.split("=").slice(1, line.split("=").length).join("="),
|
||||
"shared",
|
||||
]);
|
||||
setData(newData);
|
||||
setButtonReady(true);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
return loading ? (
|
||||
<div className="flex items-center justify-center pt-16 mb-16">
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="google logo"
|
||||
></Image>
|
||||
</div>
|
||||
) : keysExist ? (
|
||||
<div
|
||||
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
|
||||
onDragEnter={(e) => handleDragEnter(e)}
|
||||
onDragOver={(e) => handleDragOver(e)}
|
||||
onDragLeave={(e) => handleDragLeave(e)}
|
||||
onDrop={(e) => handleDrop(e)}
|
||||
>
|
||||
<input
|
||||
id="fileSelect"
|
||||
type="file"
|
||||
className="opacity-0 absolute w-full h-full"
|
||||
accept=".txt,.env"
|
||||
onChange={(e) => handleFileSelect(e)}
|
||||
/>
|
||||
{errorDragAndDrop ? (
|
||||
<div className="my-3 max-w-xl opacity-80"></div>
|
||||
) : (
|
||||
<div className=""></div>
|
||||
)}
|
||||
<div className="flex flex-row">
|
||||
<FontAwesomeIcon
|
||||
icon={faUpload}
|
||||
className="text-gray-300 text-3xl mr-6"
|
||||
/>
|
||||
<p className="text-gray-300 mt-1">
|
||||
Drag and drop your .env file here to add more keys.
|
||||
</p>
|
||||
</div>
|
||||
{errorDragAndDrop ? (
|
||||
<div className="mt-8 max-w-xl opacity-80">
|
||||
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4"
|
||||
onDragEnter={(e) => handleDragEnter(e)}
|
||||
onDragOver={(e) => handleDragOver(e)}
|
||||
onDragLeave={(e) => handleDragLeave(e)}
|
||||
onDrop={(e) => handleDrop(e)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
|
||||
<p className="">Drag and drop your .env file here.</p>
|
||||
<input
|
||||
id="fileSelect"
|
||||
type="file"
|
||||
className="opacity-0 absolute w-full h-full"
|
||||
accept=".txt,.env"
|
||||
onChange={(e) => handleFileSelect(e)}
|
||||
/>
|
||||
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
|
||||
<div className="border-t border-gray-700 w-1/5"></div>
|
||||
<p className="text-gray-400 text-xs mx-4">OR</p>
|
||||
<div className="border-t border-gray-700 w-1/5"></div>
|
||||
</div>
|
||||
<div className="z-10 mb-6">
|
||||
<Button
|
||||
color="mineshaft"
|
||||
text="Create a new .env file"
|
||||
onButtonPressed={createNewFile}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
{errorDragAndDrop ? (
|
||||
<div className="opacity-80">
|
||||
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-3">
|
||||
{/* <p className="text-xs text-gray-500"> If you are expecting to see a file here, contact your administrator for permission. </p> */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropZone;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user