mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
195 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bf88b90c3 | ||
|
|
947d2217df | ||
|
|
9eba4f8b84 | ||
|
|
f0d0e4c1e2 | ||
|
|
24cb47bcb1 | ||
|
|
9cf4a5e7a3 | ||
|
|
7b91d67655 | ||
|
|
ca0acf0445 | ||
|
|
576ff02773 | ||
|
|
d8b28107cd | ||
|
|
9898cac2f5 | ||
|
|
dd9bed4c2b | ||
|
|
14c8ae675a | ||
|
|
bda0689e18 | ||
|
|
7e39be4ca1 | ||
|
|
2e3a7c6164 | ||
|
|
f02f75e3a0 | ||
|
|
fe51dd6b0a | ||
|
|
2724336cad | ||
|
|
12bd017d07 | ||
|
|
a2eff67d44 | ||
|
|
71555a15f8 | ||
|
|
c681aa2e9f | ||
|
|
1f81ebd4fe | ||
|
|
d243470029 | ||
|
|
054e581023 | ||
|
|
f866250c25 | ||
|
|
ee58672d58 | ||
|
|
135894da2a | ||
|
|
06c8688ddd | ||
|
|
d8474b8aa3 | ||
|
|
9a4b474cdc | ||
|
|
1519a71535 | ||
|
|
9e47103131 | ||
|
|
ef689f06d6 | ||
|
|
54c7572447 | ||
|
|
4cacc6b3d1 | ||
|
|
8b193d4317 | ||
|
|
e72add74c3 | ||
|
|
115c8641e7 | ||
|
|
0af532f87e | ||
|
|
d1bd2b29fe | ||
|
|
734a6607c8 | ||
|
|
d3a2b03bb7 | ||
|
|
9a1436d0ae | ||
|
|
087e2c81cc | ||
|
|
32f35a6ca0 | ||
|
|
7fd35999b1 | ||
|
|
fa5b75e6fb | ||
|
|
7262e0debe | ||
|
|
13c686c228 | ||
|
|
083bb7b87d | ||
|
|
1b7ecd5a41 | ||
|
|
56b52b3f9c | ||
|
|
88f67b1c71 | ||
|
|
75bd10cbd5 | ||
|
|
d3397cfbd0 | ||
|
|
a4bf48fa68 | ||
|
|
dfe3294088 | ||
|
|
5b1ca4eafc | ||
|
|
82721251cc | ||
|
|
cd051b72fc | ||
|
|
411070cfcc | ||
|
|
8230c1ba91 | ||
|
|
8b7df6ce16 | ||
|
|
6d71eac221 | ||
|
|
fd3e4a8bc7 | ||
|
|
c13eb65b5a | ||
|
|
bb13a09def | ||
|
|
94bcea36c6 | ||
|
|
028b9b3f7b | ||
|
|
b85639f98e | ||
|
|
7c981b2aac | ||
|
|
59b072e7e0 | ||
|
|
6780fa9688 | ||
|
|
ff47a157c7 | ||
|
|
3601abc4c1 | ||
|
|
b1a48d4636 | ||
|
|
a15eb3b229 | ||
|
|
c34c4b244e | ||
|
|
4991e4b1f2 | ||
|
|
b3b7439617 | ||
|
|
b7c061dcb4 | ||
|
|
e00cbaeb8a | ||
|
|
655e29d96b | ||
|
|
7240ff38f1 | ||
|
|
4a08bacba0 | ||
|
|
54aaa511d5 | ||
|
|
701319efdd | ||
|
|
0fdf176648 | ||
|
|
f62d835f57 | ||
|
|
5bb4710952 | ||
|
|
9b71ce9388 | ||
|
|
175d1ec432 | ||
|
|
8454e4f781 | ||
|
|
2e79c7230f | ||
|
|
7ddd3bb8a0 | ||
|
|
b889a9d248 | ||
|
|
fc21c96cd1 | ||
|
|
0341b19c9f | ||
|
|
43095f2435 | ||
|
|
ad696ea54a | ||
|
|
f6128bdf0c | ||
|
|
6be6ec940a | ||
|
|
ba9fc59805 | ||
|
|
63a1039439 | ||
|
|
d52692c6a3 | ||
|
|
b4511ca7a2 | ||
|
|
a3c24f1f2a | ||
|
|
ca599f27f7 | ||
|
|
f36de7b2f5 | ||
|
|
0ce055c001 | ||
|
|
fc011a5661 | ||
|
|
60d6d781be | ||
|
|
9270739eb6 | ||
|
|
87b87b85c0 | ||
|
|
496fd40fa3 | ||
|
|
25fe080582 | ||
|
|
1c41091372 | ||
|
|
9230178005 | ||
|
|
fd092f1248 | ||
|
|
736c186a66 | ||
|
|
3d348ee762 | ||
|
|
6e78f49c2f | ||
|
|
e77b30671b | ||
|
|
244e1227c4 | ||
|
|
9934dac203 | ||
|
|
44ee326057 | ||
|
|
18f892096b | ||
|
|
9954d5b209 | ||
|
|
e0bde5cec9 | ||
|
|
2d4eaeb8b5 | ||
|
|
787506fb6b | ||
|
|
50c8c3a43a | ||
|
|
1f09c06274 | ||
|
|
bb59a0cd3f | ||
|
|
1e4217315b | ||
|
|
b373aca0ff | ||
|
|
87e90cb30b | ||
|
|
135fabb852 | ||
|
|
b0b5b94bb7 | ||
|
|
8f1c1e5202 | ||
|
|
e4c243d7a6 | ||
|
|
c3bca21d14 | ||
|
|
1befdb76e7 | ||
|
|
35652c5c53 | ||
|
|
9524609092 | ||
|
|
38c16fe839 | ||
|
|
c0587b9409 | ||
|
|
e249e878f6 | ||
|
|
2d64815c12 | ||
|
|
af11bc8cd2 | ||
|
|
6779dec1ff | ||
|
|
191a6112ce | ||
|
|
1bf518f768 | ||
|
|
d0d4182fc1 | ||
|
|
736f7c2d77 | ||
|
|
6e38508477 | ||
|
|
afe8160d46 | ||
|
|
ceb4ae62e2 | ||
|
|
67d0dd5bf7 | ||
|
|
6e28545c3f | ||
|
|
382208ae13 | ||
|
|
906e8de13b | ||
|
|
7a5c71cda3 | ||
|
|
7bc6d0777d | ||
|
|
f684ba7b1f | ||
|
|
86ed884162 | ||
|
|
4ff178ea34 | ||
|
|
2eb5c331a1 | ||
|
|
79ad0818f5 | ||
|
|
84c10eec66 | ||
|
|
61673a40e3 | ||
|
|
363ba1d20e | ||
|
|
5fadd73732 | ||
|
|
44e6a117dd | ||
|
|
86bb119052 | ||
|
|
54c70ff177 | ||
|
|
eabe14e4c3 | ||
|
|
342ff4b589 | ||
|
|
680811357b | ||
|
|
9d0edd810e | ||
|
|
35ff65a871 | ||
|
|
675fbb7692 | ||
|
|
295bd06918 | ||
|
|
89635fa27b | ||
|
|
60b19616c1 | ||
|
|
3884dc9259 | ||
|
|
e8648732be | ||
|
|
0f0f32a40d | ||
|
|
1b91376f5e | ||
|
|
aa347abfcd | ||
|
|
e49b190c32 | ||
|
|
66017c8623 | ||
|
|
bab93f8145 |
72
.circleci/canary-config.yml
Normal file
72
.circleci/canary-config.yml
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
version: 2.1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-amd64:
|
||||||
|
machine:
|
||||||
|
image: ubuntu-2004:current
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Prepare .env file
|
||||||
|
command: |
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
- run:
|
||||||
|
name: Build and push AMD64 image
|
||||||
|
command: |
|
||||||
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
|
echo $VERSION
|
||||||
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
|
docker build --platform linux/amd64 -t dokploy/dokploy:canary-amd64 .
|
||||||
|
docker push dokploy/dokploy:canary-amd64
|
||||||
|
|
||||||
|
build-arm64:
|
||||||
|
machine:
|
||||||
|
image: ubuntu-2004:current
|
||||||
|
resource_class: arm.large
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Prepare .env file
|
||||||
|
command: |
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
- run:
|
||||||
|
name: Build and push ARM64 image
|
||||||
|
command: |
|
||||||
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
|
docker build --platform linux/arm64 -t dokploy/dokploy:canary-arm64 .
|
||||||
|
docker push dokploy/dokploy:canary-arm64
|
||||||
|
|
||||||
|
combine-manifests:
|
||||||
|
docker:
|
||||||
|
- image: cimg/base:stable
|
||||||
|
steps:
|
||||||
|
- setup_remote_docker
|
||||||
|
- run:
|
||||||
|
name: Create and push multi-arch manifest
|
||||||
|
command: |
|
||||||
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
|
docker manifest create dokploy/dokploy:canary \
|
||||||
|
dokploy/dokploy:canary-amd64 \
|
||||||
|
dokploy/dokploy:canary-arm64
|
||||||
|
docker manifest push dokploy/dokploy:canary
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
build-all:
|
||||||
|
jobs:
|
||||||
|
- build-amd64:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: feat/circle
|
||||||
|
- build-arm64:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: feat/circle
|
||||||
|
- combine-manifests:
|
||||||
|
requires:
|
||||||
|
- build-amd64
|
||||||
|
- build-arm64
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: feat/circle
|
||||||
104
.circleci/config.yml
Normal file
104
.circleci/config.yml
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
version: 2.1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-amd64:
|
||||||
|
machine:
|
||||||
|
image: ubuntu-2004:current
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Prepare .env file
|
||||||
|
command: |
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
- run:
|
||||||
|
name: Build and push AMD64 image
|
||||||
|
command: |
|
||||||
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
|
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||||
|
TAG="latest"
|
||||||
|
else
|
||||||
|
TAG="canary"
|
||||||
|
fi
|
||||||
|
docker build --platform linux/amd64 -t dokploy/dokploy:${TAG}-amd64 .
|
||||||
|
docker push dokploy/dokploy:${TAG}-amd64
|
||||||
|
|
||||||
|
build-arm64:
|
||||||
|
machine:
|
||||||
|
image: ubuntu-2004:current
|
||||||
|
resource_class: arm.large
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Prepare .env file
|
||||||
|
command: |
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
- run:
|
||||||
|
name: Build and push ARM64 image
|
||||||
|
command: |
|
||||||
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
|
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||||
|
TAG="latest"
|
||||||
|
else
|
||||||
|
TAG="canary"
|
||||||
|
fi
|
||||||
|
docker build --platform linux/arm64 -t dokploy/dokploy:${TAG}-arm64 .
|
||||||
|
docker push dokploy/dokploy:${TAG}-arm64
|
||||||
|
|
||||||
|
combine-manifests:
|
||||||
|
docker:
|
||||||
|
- image: cimg/node:18.18.0
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- setup_remote_docker
|
||||||
|
- run:
|
||||||
|
name: Create and push multi-arch manifest
|
||||||
|
command: |
|
||||||
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
|
|
||||||
|
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||||
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
|
echo $VERSION
|
||||||
|
TAG="latest"
|
||||||
|
|
||||||
|
docker manifest create dokploy/dokploy:${TAG} \
|
||||||
|
dokploy/dokploy:${TAG}-amd64 \
|
||||||
|
dokploy/dokploy:${TAG}-arm64
|
||||||
|
docker manifest push dokploy/dokploy:${TAG}
|
||||||
|
|
||||||
|
docker manifest create dokploy/dokploy:${VERSION} \
|
||||||
|
dokploy/dokploy:${TAG}-amd64 \
|
||||||
|
dokploy/dokploy:${TAG}-arm64
|
||||||
|
docker manifest push dokploy/dokploy:${VERSION}
|
||||||
|
else
|
||||||
|
TAG="canary"
|
||||||
|
docker manifest create dokploy/dokploy:${TAG} \
|
||||||
|
dokploy/dokploy:${TAG}-amd64 \
|
||||||
|
dokploy/dokploy:${TAG}-arm64
|
||||||
|
docker manifest push dokploy/dokploy:${TAG}
|
||||||
|
fi
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
build-all:
|
||||||
|
jobs:
|
||||||
|
- build-amd64:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- main
|
||||||
|
- canary
|
||||||
|
- build-arm64:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- main
|
||||||
|
- canary
|
||||||
|
- combine-manifests:
|
||||||
|
requires:
|
||||||
|
- build-amd64
|
||||||
|
- build-arm64
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- main
|
||||||
|
- canary
|
||||||
76
.circleci/main-config.yml
Normal file
76
.circleci/main-config.yml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
version: 2.1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-amd64:
|
||||||
|
machine:
|
||||||
|
image: ubuntu-2004:current
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Prepare .env file
|
||||||
|
command: |
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
- run:
|
||||||
|
name: Build and push AMD64 image
|
||||||
|
command: |
|
||||||
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
|
docker build --platform linux/amd64 -t dokploy/dokploy:latest-amd64 .
|
||||||
|
docker push dokploy/dokploy:latest-amd64
|
||||||
|
|
||||||
|
build-arm64:
|
||||||
|
machine:
|
||||||
|
image: ubuntu-2004:current
|
||||||
|
resource_class: arm.large
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Prepare .env file
|
||||||
|
command: |
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
- run:
|
||||||
|
name: Build and push ARM64 image
|
||||||
|
command: |
|
||||||
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
|
docker build --platform linux/arm64 -t dokploy/dokploy:latest-arm64 .
|
||||||
|
docker push dokploy/dokploy:latest-arm64
|
||||||
|
|
||||||
|
combine-manifests:
|
||||||
|
docker:
|
||||||
|
- image: cimg/base:stable
|
||||||
|
steps:
|
||||||
|
- setup_remote_docker
|
||||||
|
- run:
|
||||||
|
name: Create and push multi-arch manifest
|
||||||
|
command: |
|
||||||
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
|
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||||
|
docker manifest create dokploy/dokploy:latest \
|
||||||
|
dokploy/dokploy:latest-amd64 \
|
||||||
|
dokploy/dokploy:latest-arm64
|
||||||
|
docker manifest push dokploy/dokploy:latest
|
||||||
|
|
||||||
|
docker manifest create dokploy/dokploy:${VERSION} \
|
||||||
|
dokploy/dokploy:latest-amd64 \
|
||||||
|
dokploy/dokploy:latest-arm64
|
||||||
|
docker manifest push dokploy/dokploy:${VERSION}
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
build-all:
|
||||||
|
jobs:
|
||||||
|
- build-amd64:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: main
|
||||||
|
- build-arm64:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: main
|
||||||
|
- combine-manifests:
|
||||||
|
requires:
|
||||||
|
- build-amd64
|
||||||
|
- build-arm64
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: main
|
||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: [siumauricio]
|
||||||
patreon: #
|
patreon: #
|
||||||
open_collective: dokploy
|
open_collective: dokploy
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
|||||||
76
.github/workflows/pull-request.yml
vendored
76
.github/workflows/pull-request.yml
vendored
@@ -1,59 +1,49 @@
|
|||||||
name: Pull request
|
name: Pull request
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- canary
|
|
||||||
|
|
||||||
push:
|
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- canary
|
- canary
|
||||||
|
|
||||||
|
env:
|
||||||
|
HUSKY: 0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-app:
|
build-app:
|
||||||
if: github.event_name == 'pull_request'
|
runs-on: ubuntu-latest
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [18.18.0]
|
node-version: [18.18.0]
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: pnpm/action-setup@v3
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
cache: 'pnpm'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
- name: Run Build
|
|
||||||
run: pnpm build
|
|
||||||
- name: Run Tests
|
|
||||||
run: pnpm run test
|
|
||||||
|
|
||||||
build-and-push-docker-on-push:
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the code
|
- name: Check out the code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
fetch-depth: 0
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Prepare .env file
|
- name: Setup pnpm
|
||||||
run: |
|
uses: pnpm/action-setup@v4
|
||||||
cp .env.production.example .env.production
|
|
||||||
|
|
||||||
- name: Build and push Docker image using custom script
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
run: |
|
uses: actions/setup-node@v4
|
||||||
chmod +x ./docker/push.sh
|
with:
|
||||||
./docker/push.sh ${{ github.ref_name == 'canary' && 'canary' || '' }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
# - name: Run commitlint
|
||||||
|
# run: pnpm commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose
|
||||||
|
|
||||||
|
- name: Run format and lint
|
||||||
|
run: pnpm biome ci
|
||||||
|
|
||||||
|
- name: Run type check
|
||||||
|
run: pnpm typecheck
|
||||||
|
|
||||||
|
- name: Run Build
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: pnpm run test
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -52,6 +52,7 @@ yarn-error.log*
|
|||||||
# otros
|
# otros
|
||||||
/.data
|
/.data
|
||||||
/.main
|
/.main
|
||||||
|
.vscode
|
||||||
|
|
||||||
*.lockb
|
*.lockb
|
||||||
*.rdb
|
*.rdb
|
||||||
|
|||||||
4
.husky/commit-msg
Normal file
4
.husky/commit-msg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
pnpm commitlint --edit $1
|
||||||
6
.husky/install.mjs
Normal file
6
.husky/install.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Skip Husky install in production and CI
|
||||||
|
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
const husky = (await import("husky")).default;
|
||||||
|
console.log(husky());
|
||||||
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pnpm lint-staged
|
||||||
72
Dockerfile
72
Dockerfile
@@ -1,48 +1,65 @@
|
|||||||
# Etapa 1: Prepare image for building
|
|
||||||
FROM node:18-slim AS base
|
FROM node:18-slim AS base
|
||||||
|
|
||||||
# Install dependencies
|
# Disable husky
|
||||||
|
ENV HUSKY=0
|
||||||
|
|
||||||
|
# Set pnpm home
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
RUN corepack enable && apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
|
# Enable corepack
|
||||||
|
RUN corepack enable
|
||||||
|
|
||||||
|
# Set workdir
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
FROM base AS base-deps
|
||||||
|
# Install dependencies only for production
|
||||||
|
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy install script for husky
|
||||||
|
COPY .husky/install.mjs ./.husky/install.mjs
|
||||||
|
|
||||||
# Copy package.json and pnpm-lock.yaml
|
# Copy package.json and pnpm-lock.yaml
|
||||||
COPY package.json pnpm-lock.yaml ./
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
FROM base-deps AS prod-deps
|
||||||
|
|
||||||
|
# Set production
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# Install dependencies only for production
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||||
|
|
||||||
|
FROM base-deps AS build
|
||||||
|
|
||||||
# Install dependencies only for building
|
# Install dependencies only for building
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||||
|
|
||||||
# Copy the rest of the source code
|
# Copy the rest of the source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the application
|
# Build the application
|
||||||
RUN pnpm run build
|
RUN pnpm build
|
||||||
|
|
||||||
# Stage 2: Prepare image for production
|
FROM base AS production
|
||||||
FROM node:18-slim AS production
|
|
||||||
|
# Set production
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
# Install dependencies only for production
|
# Install dependencies only for production
|
||||||
ENV PNPM_HOME="/pnpm"
|
RUN apt-get update && apt-get install -y curl apache2-utils && rm -rf /var/lib/apt/lists/*
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
|
||||||
RUN corepack enable && apt-get update && apt-get install -y curl && apt-get install -y apache2-utils && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
# Copy the rest of the source code
|
||||||
|
COPY --from=build /app/.next ./.next
|
||||||
# Copy the rest of the source code
|
COPY --from=build /app/dist ./dist
|
||||||
COPY --from=base /app/.next ./.next
|
COPY --from=build /app/next.config.mjs ./next.config.mjs
|
||||||
COPY --from=base /app/dist ./dist
|
COPY --from=build /app/public ./public
|
||||||
COPY --from=base /app/next.config.mjs ./next.config.mjs
|
COPY --from=build /app/package.json ./package.json
|
||||||
COPY --from=base /app/public ./public
|
COPY --from=build /app/drizzle ./drizzle
|
||||||
COPY --from=base /app/package.json ./package.json
|
COPY --from=build /app/.env.production ./.env
|
||||||
COPY --from=base /app/drizzle ./drizzle
|
COPY --from=build /app/components.json ./components.json
|
||||||
COPY --from=base /app/.env.production ./.env
|
COPY --from=prod-deps /app/node_modules ./node_modules
|
||||||
COPY --from=base /app/components.json ./components.json
|
|
||||||
|
|
||||||
# Install dependencies only for production
|
|
||||||
COPY package.json pnpm-lock.yaml ./
|
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
|
||||||
|
|
||||||
# Install docker
|
# Install docker
|
||||||
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh
|
RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm get-docker.sh
|
||||||
@@ -54,11 +71,10 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
|
|||||||
&& ./install.sh \
|
&& ./install.sh \
|
||||||
&& pnpm install -g tsx
|
&& pnpm install -g tsx
|
||||||
|
|
||||||
|
|
||||||
# Install buildpacks
|
# Install buildpacks
|
||||||
RUN curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
RUN curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.35.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["pnpm", "start"]
|
CMD ["pnpm", "start"]
|
||||||
|
|||||||
53
README.md
53
README.md
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h1 align="center">Dokploy</h1>
|
<h1 align="center">Dokploy</h1>
|
||||||
<div>
|
<div>
|
||||||
@@ -11,74 +10,67 @@
|
|||||||
<br />
|
<br />
|
||||||
Dokploy is a free self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
|
Dokploy is a free self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
Dokploy include multiples features to make your life easier.
|
Dokploy include multiples features to make your life easier.
|
||||||
|
|
||||||
|
- **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.).
|
||||||
* **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.).
|
- **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, Redis.
|
||||||
* **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, Redis.
|
- **Backups**: Automate backups for databases to a external storage destination.
|
||||||
* **Backups**: Automate backups for databases to a external storage destination.
|
- **Docker Compose**: Native support for Docker Compose to manage complex applications.
|
||||||
* **Docker Compose**: Native support for Docker Compose to manage complex applications.
|
- **Multi Node**: Scale applications to multiples nodes using docker swarm to manage the cluster.
|
||||||
* **Multi Node**: Scale applications to multiples nodes using docker swarm to manage the cluster.
|
- **Templates**: Deploy in a single click open source templates (Plausible, Pocketbase, Calcom, etc.).
|
||||||
* **Templates**: Deploy in a single click open source templates (Plausible, Pocketbase, Calcom, etc.).
|
- **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
||||||
* **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
- **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
|
||||||
* **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
|
- **Docker Management**: Easily deploy and manage Docker containers.
|
||||||
* **Docker Management**: Easily deploy and manage Docker containers.
|
- **CLI/API**: Manage your applications and databases using the command line or trought the API.
|
||||||
* **CLI/API**: Manage your applications and databases using the command line or trought the API.
|
- **Self-Hosted**: Self-host Dokploy on your VPS.
|
||||||
* **Self-Hosted**: Self-host Dokploy on your VPS.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 🚀 Getting Started
|
## 🚀 Getting Started
|
||||||
|
|
||||||
To get started run the following command in a VPS:
|
To get started run the following command in a VPS:
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sSL https://dokploy.com/install.sh | sh
|
curl -sSL https://dokploy.com/install.sh | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 📄 Documentation
|
## 📄 Documentation
|
||||||
|
|
||||||
For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
||||||
|
|
||||||
|
|
||||||
## Video Tutorial
|
## Video Tutorial
|
||||||
|
|
||||||
<a href="https://youtu.be/mznYKPvhcfw">
|
<a href="https://youtu.be/mznYKPvhcfw">
|
||||||
<img src="https://dokploy.com/banner.webp" alt="Watch the video" width="400" style="border-radius:20px;"/>
|
<img src="https://dokploy.com/banner.webp" alt="Watch the video" width="400" style="border-radius:20px;"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
## Donations
|
## Donations
|
||||||
|
|
||||||
If you like dokploy, and want to support the project to cover the costs of hosting, testing and development new features, you can donate to the project using the following link:
|
If you like dokploy, and want to support the project to cover the costs of hosting, testing and development new features, you can donate to the project using the following link:
|
||||||
|
|
||||||
Thanks to all the supporters!
|
Thanks to all the supporters!
|
||||||
|
|
||||||
https://opencollective.com/dokploy
|
[Dokploy Open Collective](https://opencollective.com/dokploy)
|
||||||
|
|
||||||
|
Organizations:
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/dokploy"><img src="https://opencollective.com/dokploy/organizations.svg?width=890"></a>
|
||||||
|
|
||||||
|
Individuals:
|
||||||
<a href="https://opencollective.com/dokploy"><img src="https://opencollective.com/dokploy/individuals.svg?width=890"></a>
|
<a href="https://opencollective.com/dokploy"><img src="https://opencollective.com/dokploy/individuals.svg?width=890"></a>
|
||||||
|
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
<a href="https://github.com/dokploy/dokploy/graphs/contributors">
|
<a href="https://github.com/dokploy/dokploy/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" />
|
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Support OS
|
## Support OS
|
||||||
|
|
||||||
- Ubuntu 24.04 LTS
|
- Ubuntu 24.04 LTS
|
||||||
- Ubuntu 23.10
|
- Ubuntu 23.10
|
||||||
- Ubuntu 22.04 LTS
|
- Ubuntu 22.04 LTS
|
||||||
- Ubuntu 20.04 LTS
|
- Ubuntu 20.04 LTS
|
||||||
- Ubuntu 18.04 LTS
|
- Ubuntu 18.04 LTS
|
||||||
- Debian 12
|
- Debian 12
|
||||||
- Debian 11
|
- Debian 11
|
||||||
@@ -86,9 +78,6 @@ https://opencollective.com/dokploy
|
|||||||
- Centos 9
|
- Centos 9
|
||||||
- Centos 8
|
- Centos 8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
[English](README.md) | [中文](README-zh.md) | [Deutsch](README-de.md) | [Русский Язык](README-ru.md)
|
[English](README.md) | [中文](README-zh.md) | [Deutsch](README-de.md) | [Русский Язык](README-ru.md)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { expect, test } from "vitest";
|
|
||||||
import { load } from "js-yaml";
|
|
||||||
import { addPrefixToAllProperties } from "@/server/utils/docker/compose";
|
import { addPrefixToAllProperties } from "@/server/utils/docker/compose";
|
||||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||||
|
import { load } from "js-yaml";
|
||||||
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
const composeFile1 = `
|
const composeFile1 = `
|
||||||
version: "3.8"
|
version: "3.8"
|
||||||
|
|||||||
@@ -79,10 +79,11 @@ test("Add prefix to networks in services with aliases", () => {
|
|||||||
`frontend-${prefix}`,
|
`frontend-${prefix}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const networkConfig =
|
const networkConfig = actualComposeData?.services?.api?.networks as {
|
||||||
actualComposeData?.services?.api?.networks[`frontend-${prefix}`];
|
[key: string]: { aliases?: string[] };
|
||||||
expect(networkConfig).toBeDefined();
|
};
|
||||||
expect(networkConfig?.aliases).toContain("api");
|
expect(networkConfig[`frontend-${prefix}`]).toBeDefined();
|
||||||
|
expect(networkConfig[`frontend-${prefix}`]?.aliases).toContain("api");
|
||||||
|
|
||||||
expect(actualComposeData.services?.api?.networks).not.toHaveProperty(
|
expect(actualComposeData.services?.api?.networks).not.toHaveProperty(
|
||||||
"frontend-ash",
|
"frontend-ash",
|
||||||
@@ -169,7 +170,9 @@ test("Add prefix to networks in services (combined case)", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Caso 2: Objeto con aliases
|
// Caso 2: Objeto con aliases
|
||||||
const apiNetworks = actualComposeData.services?.api?.networks;
|
const apiNetworks = actualComposeData.services?.api?.networks as {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
expect(apiNetworks).toHaveProperty(`frontend-${prefix}`);
|
expect(apiNetworks).toHaveProperty(`frontend-${prefix}`);
|
||||||
expect(apiNetworks[`frontend-${prefix}`]).toBeDefined();
|
expect(apiNetworks[`frontend-${prefix}`]).toBeDefined();
|
||||||
expect(apiNetworks).not.toHaveProperty("frontend");
|
expect(apiNetworks).not.toHaveProperty("frontend");
|
||||||
|
|||||||
@@ -76,9 +76,11 @@ test("Add prefix to networks in services and root (combined case)", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Caso 2: Objeto con aliases
|
// Caso 2: Objeto con aliases
|
||||||
const apiNetworks = actualComposeData.services?.api?.networks;
|
const apiNetworks = actualComposeData.services?.api?.networks as {
|
||||||
|
[key: string]: { aliases?: string[] };
|
||||||
|
};
|
||||||
expect(apiNetworks).toHaveProperty(`frontend-${prefix}`);
|
expect(apiNetworks).toHaveProperty(`frontend-${prefix}`);
|
||||||
expect(apiNetworks[`frontend-${prefix}`]?.aliases).toContain("api");
|
expect(apiNetworks?.[`frontend-${prefix}`]?.aliases).toContain("api");
|
||||||
expect(apiNetworks).not.toHaveProperty("frontend");
|
expect(apiNetworks).not.toHaveProperty("frontend");
|
||||||
|
|
||||||
// Caso 3: Objeto con redes simples
|
// Caso 3: Objeto con redes simples
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { expect, test } from "vitest";
|
|
||||||
import { load, dump } from "js-yaml";
|
|
||||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
|
||||||
import { addPrefixToSecretsRoot } from "@/server/utils/docker/compose/secrets";
|
import { addPrefixToSecretsRoot } from "@/server/utils/docker/compose/secrets";
|
||||||
|
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||||
|
import { dump, load } from "js-yaml";
|
||||||
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
test("Generate random hash with 8 characters", () => {
|
test("Generate random hash with 8 characters", () => {
|
||||||
const hash = generateRandomHash();
|
const hash = generateRandomHash();
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ test("Add prefix to service names with container_name in compose file", () => {
|
|||||||
const actualComposeData = { ...composeData, services: updatedComposeData };
|
const actualComposeData = { ...composeData, services: updatedComposeData };
|
||||||
|
|
||||||
// Verificar que el nombre del contenedor ha cambiado correctamente
|
// Verificar que el nombre del contenedor ha cambiado correctamente
|
||||||
expect(actualComposeData.services[`web-${prefix}`].container_name).toBe(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.container_name).toBe(
|
||||||
`web_container-${prefix}`,
|
`web_container-${prefix}`,
|
||||||
);
|
);
|
||||||
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
// Verificar que la nueva clave del servicio tiene el prefijo y la vieja clave no existe
|
||||||
@@ -50,10 +50,10 @@ test("Add prefix to service names with container_name in compose file", () => {
|
|||||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||||
|
|
||||||
// Verificar que la configuración de la imagen sigue igual
|
// Verificar que la configuración de la imagen sigue igual
|
||||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||||
"nginx:latest",
|
"nginx:latest",
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||||
"myapi:latest",
|
"myapi:latest",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,30 +51,30 @@ test("Add prefix to service names with depends_on (array) in compose file", () =
|
|||||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||||
|
|
||||||
// Verificar que la configuración de la imagen sigue igual
|
// Verificar que la configuración de la imagen sigue igual
|
||||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||||
"nginx:latest",
|
"nginx:latest",
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||||
"myapi:latest",
|
"myapi:latest",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verificar que los nombres en depends_on tienen el prefijo
|
// Verificar que los nombres en depends_on tienen el prefijo
|
||||||
expect(actualComposeData.services[`web-${prefix}`].depends_on).toContain(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.depends_on).toContain(
|
||||||
`db-${prefix}`,
|
`db-${prefix}`,
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services[`web-${prefix}`].depends_on).toContain(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.depends_on).toContain(
|
||||||
`api-${prefix}`,
|
`api-${prefix}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||||
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
|
||||||
"postgres:latest",
|
"postgres:latest",
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||||
"myapi:latest",
|
"myapi:latest",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -121,16 +121,16 @@ test("Add prefix to service names with depends_on (object) in compose file", ()
|
|||||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||||
|
|
||||||
// Verificar que la configuración de la imagen sigue igual
|
// Verificar que la configuración de la imagen sigue igual
|
||||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||||
"nginx:latest",
|
"nginx:latest",
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||||
"myapi:latest",
|
"myapi:latest",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verificar que los nombres en depends_on tienen el prefijo
|
// Verificar que los nombres en depends_on tienen el prefijo
|
||||||
const webDependsOn = actualComposeData.services[`web-${prefix}`]
|
const webDependsOn = actualComposeData.services?.[`web-${prefix}`]
|
||||||
.depends_on as Record<string, any>;
|
?.depends_on as Record<string, any>;
|
||||||
expect(webDependsOn).toHaveProperty(`db-${prefix}`);
|
expect(webDependsOn).toHaveProperty(`db-${prefix}`);
|
||||||
expect(webDependsOn).toHaveProperty(`api-${prefix}`);
|
expect(webDependsOn).toHaveProperty(`api-${prefix}`);
|
||||||
expect(webDependsOn[`db-${prefix}`].condition).toBe("service_healthy");
|
expect(webDependsOn[`db-${prefix}`].condition).toBe("service_healthy");
|
||||||
@@ -139,12 +139,12 @@ test("Add prefix to service names with depends_on (object) in compose file", ()
|
|||||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||||
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
|
||||||
"postgres:latest",
|
"postgres:latest",
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||||
"myapi:latest",
|
"myapi:latest",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,22 +49,22 @@ test("Add prefix to service names with extends (string) in compose file", () =>
|
|||||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||||
|
|
||||||
// Verificar que la configuración de la imagen sigue igual
|
// Verificar que la configuración de la imagen sigue igual
|
||||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||||
"nginx:latest",
|
"nginx:latest",
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||||
"myapi:latest",
|
"myapi:latest",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verificar que el nombre en extends tiene el prefijo
|
// Verificar que el nombre en extends tiene el prefijo
|
||||||
expect(actualComposeData.services[`web-${prefix}`].extends).toBe(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.extends).toBe(
|
||||||
`base_service-${prefix}`,
|
`base_service-${prefix}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verificar que el servicio `base_service` también tiene el prefijo
|
// Verificar que el servicio `base_service` también tiene el prefijo
|
||||||
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
|
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
|
||||||
expect(actualComposeData.services).not.toHaveProperty("base_service");
|
expect(actualComposeData.services).not.toHaveProperty("base_service");
|
||||||
expect(actualComposeData.services[`base_service-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`base_service-${prefix}`]?.image).toBe(
|
||||||
"base:latest",
|
"base:latest",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -109,23 +109,23 @@ test("Add prefix to service names with extends (object) in compose file", () =>
|
|||||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||||
|
|
||||||
// Verificar que la configuración de la imagen sigue igual
|
// Verificar que la configuración de la imagen sigue igual
|
||||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||||
"nginx:latest",
|
"nginx:latest",
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||||
"myapi:latest",
|
"myapi:latest",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verificar que el nombre en extends.service tiene el prefijo
|
// Verificar que el nombre en extends.service tiene el prefijo
|
||||||
const webExtends = actualComposeData.services[`web-${prefix}`].extends;
|
const webExtends = actualComposeData.services?.[`web-${prefix}`]?.extends;
|
||||||
if (typeof webExtends !== "string") {
|
if (typeof webExtends !== "string") {
|
||||||
expect(webExtends.service).toBe(`base_service-${prefix}`);
|
expect(webExtends?.service).toBe(`base_service-${prefix}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar que el servicio `base_service` también tiene el prefijo
|
// Verificar que el servicio `base_service` también tiene el prefijo
|
||||||
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
|
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
|
||||||
expect(actualComposeData.services).not.toHaveProperty("base_service");
|
expect(actualComposeData.services).not.toHaveProperty("base_service");
|
||||||
expect(actualComposeData.services[`base_service-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`base_service-${prefix}`]?.image).toBe(
|
||||||
"base:latest",
|
"base:latest",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,27 +50,27 @@ test("Add prefix to service names with links in compose file", () => {
|
|||||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||||
|
|
||||||
// Verificar que la configuración de la imagen sigue igual
|
// Verificar que la configuración de la imagen sigue igual
|
||||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||||
"nginx:latest",
|
"nginx:latest",
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||||
"myapi:latest",
|
"myapi:latest",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verificar que los nombres en links tienen el prefijo
|
// Verificar que los nombres en links tienen el prefijo
|
||||||
expect(actualComposeData.services[`web-${prefix}`].links).toContain(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.links).toContain(
|
||||||
`db-${prefix}`,
|
`db-${prefix}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
// Verificar que los servicios `db` y `api` también tienen el prefijo
|
||||||
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
|
||||||
expect(actualComposeData.services).not.toHaveProperty("db");
|
expect(actualComposeData.services).not.toHaveProperty("db");
|
||||||
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
|
||||||
"postgres:latest",
|
"postgres:latest",
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
|
||||||
expect(actualComposeData.services).not.toHaveProperty("api");
|
expect(actualComposeData.services).not.toHaveProperty("api");
|
||||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||||
"myapi:latest",
|
"myapi:latest",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,23 +54,25 @@ test("Add prefix to service names with volumes_from in compose file", () => {
|
|||||||
expect(actualComposeData.services).not.toHaveProperty("web");
|
expect(actualComposeData.services).not.toHaveProperty("web");
|
||||||
|
|
||||||
// Verificar que la configuración de la imagen sigue igual
|
// Verificar que la configuración de la imagen sigue igual
|
||||||
expect(actualComposeData.services[`web-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.image).toBe(
|
||||||
"nginx:latest",
|
"nginx:latest",
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
|
||||||
"myapi:latest",
|
"myapi:latest",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verificar que los nombres en volumes_from tienen el prefijo
|
// Verificar que los nombres en volumes_from tienen el prefijo
|
||||||
expect(actualComposeData.services[`web-${prefix}`].volumes_from).toContain(
|
expect(actualComposeData.services?.[`web-${prefix}`]?.volumes_from).toContain(
|
||||||
`shared-${prefix}`,
|
`shared-${prefix}`,
|
||||||
);
|
);
|
||||||
expect(actualComposeData.services[`api-${prefix}`].volumes_from).toContain(
|
expect(actualComposeData.services?.[`api-${prefix}`]?.volumes_from).toContain(
|
||||||
`shared-${prefix}`,
|
`shared-${prefix}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verificar que el servicio shared también tiene el prefijo
|
// Verificar que el servicio shared también tiene el prefijo
|
||||||
expect(actualComposeData.services).toHaveProperty(`shared-${prefix}`);
|
expect(actualComposeData.services).toHaveProperty(`shared-${prefix}`);
|
||||||
expect(actualComposeData.services).not.toHaveProperty("shared");
|
expect(actualComposeData.services).not.toHaveProperty("shared");
|
||||||
expect(actualComposeData.services[`shared-${prefix}`].image).toBe("busybox");
|
expect(actualComposeData.services?.[`shared-${prefix}`]?.image).toBe(
|
||||||
|
"busybox",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { generateRandomHash } from "@/server/utils/docker/compose";
|
import { generateRandomHash } from "@/server/utils/docker/compose";
|
||||||
import {
|
import {
|
||||||
addPrefixToVolumesRoot,
|
|
||||||
addPrefixToAllVolumes,
|
addPrefixToAllVolumes,
|
||||||
|
addPrefixToVolumesRoot,
|
||||||
} from "@/server/utils/docker/compose/volume";
|
} from "@/server/utils/docker/compose/volume";
|
||||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
|
|||||||
98
__test__/drop/drop.test.test.ts
Normal file
98
__test__/drop/drop.test.test.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { APPLICATIONS_PATH } from "@/server/constants";
|
||||||
|
import { unzipDrop } from "@/server/utils/builders/drop";
|
||||||
|
import AdmZip from "adm-zip";
|
||||||
|
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
const undici = require("undici");
|
||||||
|
globalThis.File = undici.File as any;
|
||||||
|
globalThis.FileList = undici.FileList as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mock("@/server/constants", () => ({
|
||||||
|
APPLICATIONS_PATH: "./__test__/drop/zips/output",
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("unzipDrop using real zip files", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly extract a zip with a single root folder", async () => {
|
||||||
|
const appName = "single-file";
|
||||||
|
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
|
||||||
|
const zip = new AdmZip("./__test__/drop/zips/single-file.zip");
|
||||||
|
|
||||||
|
const zipBuffer = zip.toBuffer();
|
||||||
|
const file = new File([zipBuffer], "single.zip");
|
||||||
|
await unzipDrop(file, appName);
|
||||||
|
|
||||||
|
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||||
|
expect(files.some((f) => f.name === "test.txt")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly extract a zip with a single root folder and a subfolder", async () => {
|
||||||
|
const appName = "folderwithfile";
|
||||||
|
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
|
||||||
|
const zip = new AdmZip("./__test__/drop/zips/folder-with-file.zip");
|
||||||
|
|
||||||
|
const zipBuffer = zip.toBuffer();
|
||||||
|
const file = new File([zipBuffer], "single.zip");
|
||||||
|
await unzipDrop(file, appName);
|
||||||
|
|
||||||
|
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||||
|
expect(files.some((f) => f.name === "folder1.txt")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly extract a zip with multiple root folders", async () => {
|
||||||
|
const appName = "two-folders";
|
||||||
|
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
|
||||||
|
const zip = new AdmZip("./__test__/drop/zips/two-folders.zip");
|
||||||
|
|
||||||
|
const zipBuffer = zip.toBuffer();
|
||||||
|
const file = new File([zipBuffer], "single.zip");
|
||||||
|
await unzipDrop(file, appName);
|
||||||
|
|
||||||
|
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||||
|
|
||||||
|
expect(files.some((f) => f.name === "folder1")).toBe(true);
|
||||||
|
expect(files.some((f) => f.name === "folder2")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly extract a zip with a single root with a file", async () => {
|
||||||
|
const appName = "nested";
|
||||||
|
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
|
||||||
|
const zip = new AdmZip("./__test__/drop/zips/nested.zip");
|
||||||
|
|
||||||
|
const zipBuffer = zip.toBuffer();
|
||||||
|
const file = new File([zipBuffer], "single.zip");
|
||||||
|
await unzipDrop(file, appName);
|
||||||
|
|
||||||
|
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||||
|
|
||||||
|
expect(files.some((f) => f.name === "folder1")).toBe(true);
|
||||||
|
expect(files.some((f) => f.name === "folder2")).toBe(true);
|
||||||
|
expect(files.some((f) => f.name === "folder3")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly extract a zip with a single root with a folder", async () => {
|
||||||
|
const appName = "folder-with-sibling-file";
|
||||||
|
const outputPath = path.join(APPLICATIONS_PATH, appName, "code");
|
||||||
|
const zip = new AdmZip("./__test__/drop/zips/folder-with-sibling-file.zip");
|
||||||
|
|
||||||
|
const zipBuffer = zip.toBuffer();
|
||||||
|
const file = new File([zipBuffer], "single.zip");
|
||||||
|
await unzipDrop(file, appName);
|
||||||
|
|
||||||
|
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||||
|
|
||||||
|
expect(files.some((f) => f.name === "folder1")).toBe(true);
|
||||||
|
expect(files.some((f) => f.name === "test.txt")).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
BIN
__test__/drop/zips/folder-with-file.zip
Normal file
BIN
__test__/drop/zips/folder-with-file.zip
Normal file
Binary file not shown.
BIN
__test__/drop/zips/folder-with-sibling-file.zip
Normal file
BIN
__test__/drop/zips/folder-with-sibling-file.zip
Normal file
Binary file not shown.
1
__test__/drop/zips/folder1/folder1.txt
Normal file
1
__test__/drop/zips/folder1/folder1.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Gogogogogogo
|
||||||
1
__test__/drop/zips/folder2/folder2.txt
Normal file
1
__test__/drop/zips/folder2/folder2.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
gogogogogog
|
||||||
1
__test__/drop/zips/folder3/file3.txt
Normal file
1
__test__/drop/zips/folder3/file3.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
gogogogogogogogogo
|
||||||
BIN
__test__/drop/zips/nested.zip
Normal file
BIN
__test__/drop/zips/nested.zip
Normal file
Binary file not shown.
BIN
__test__/drop/zips/single-file.zip
Normal file
BIN
__test__/drop/zips/single-file.zip
Normal file
Binary file not shown.
1
__test__/drop/zips/test.txt
Normal file
1
__test__/drop/zips/test.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dsafasdfasdf
|
||||||
BIN
__test__/drop/zips/two-folders.zip
Normal file
BIN
__test__/drop/zips/two-folders.zip
Normal file
Binary file not shown.
187
__test__/traefik/traefik.test.ts
Normal file
187
__test__/traefik/traefik.test.ts
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import type { Domain } from "@/server/api/services/domain";
|
||||||
|
import type { Redirect } from "@/server/api/services/redirect";
|
||||||
|
import type { ApplicationNested } from "@/server/utils/builders";
|
||||||
|
import { createRouterConfig } from "@/server/utils/traefik/domain";
|
||||||
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
const baseApp: ApplicationNested = {
|
||||||
|
applicationId: "",
|
||||||
|
applicationStatus: "done",
|
||||||
|
appName: "",
|
||||||
|
autoDeploy: true,
|
||||||
|
branch: null,
|
||||||
|
buildArgs: null,
|
||||||
|
buildPath: "/",
|
||||||
|
buildType: "nixpacks",
|
||||||
|
command: null,
|
||||||
|
cpuLimit: null,
|
||||||
|
cpuReservation: null,
|
||||||
|
createdAt: "",
|
||||||
|
customGitBranch: "",
|
||||||
|
customGitBuildPath: "",
|
||||||
|
customGitSSHKeyId: null,
|
||||||
|
customGitUrl: "",
|
||||||
|
description: "",
|
||||||
|
dockerfile: null,
|
||||||
|
dockerImage: null,
|
||||||
|
dropBuildPath: null,
|
||||||
|
enabled: null,
|
||||||
|
env: null,
|
||||||
|
healthCheckSwarm: null,
|
||||||
|
labelsSwarm: null,
|
||||||
|
memoryLimit: null,
|
||||||
|
memoryReservation: null,
|
||||||
|
modeSwarm: null,
|
||||||
|
mounts: [],
|
||||||
|
name: "",
|
||||||
|
networkSwarm: null,
|
||||||
|
owner: null,
|
||||||
|
password: null,
|
||||||
|
placementSwarm: null,
|
||||||
|
ports: [],
|
||||||
|
projectId: "",
|
||||||
|
redirects: [],
|
||||||
|
refreshToken: "",
|
||||||
|
registry: null,
|
||||||
|
registryId: null,
|
||||||
|
replicas: 1,
|
||||||
|
repository: null,
|
||||||
|
restartPolicySwarm: null,
|
||||||
|
rollbackConfigSwarm: null,
|
||||||
|
security: [],
|
||||||
|
sourceType: "git",
|
||||||
|
subtitle: null,
|
||||||
|
title: null,
|
||||||
|
updateConfigSwarm: null,
|
||||||
|
username: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseDomain: Domain = {
|
||||||
|
applicationId: "",
|
||||||
|
certificateType: "none",
|
||||||
|
createdAt: "",
|
||||||
|
domainId: "",
|
||||||
|
host: "",
|
||||||
|
https: false,
|
||||||
|
path: null,
|
||||||
|
port: null,
|
||||||
|
uniqueConfigKey: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseRedirect: Redirect = {
|
||||||
|
redirectId: "",
|
||||||
|
regex: "",
|
||||||
|
replacement: "",
|
||||||
|
permanent: false,
|
||||||
|
uniqueConfigKey: 1,
|
||||||
|
createdAt: "",
|
||||||
|
applicationId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Middlewares */
|
||||||
|
|
||||||
|
test("Web entrypoint on http domain", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
baseApp,
|
||||||
|
{ ...baseDomain, https: false },
|
||||||
|
"web",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Web entrypoint on http domain with redirect", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
{
|
||||||
|
...baseApp,
|
||||||
|
appName: "test",
|
||||||
|
redirects: [{ ...baseRedirect, uniqueConfigKey: 1 }],
|
||||||
|
},
|
||||||
|
{ ...baseDomain, https: false },
|
||||||
|
"web",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||||
|
expect(router.middlewares).toContain("redirect-test-1");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Web entrypoint on http domain with multiple redirect", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
{
|
||||||
|
...baseApp,
|
||||||
|
appName: "test",
|
||||||
|
redirects: [
|
||||||
|
{ ...baseRedirect, uniqueConfigKey: 1 },
|
||||||
|
{ ...baseRedirect, uniqueConfigKey: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ ...baseDomain, https: false },
|
||||||
|
"web",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||||
|
expect(router.middlewares).toContain("redirect-test-1");
|
||||||
|
expect(router.middlewares).toContain("redirect-test-2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Web entrypoint on https domain", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
baseApp,
|
||||||
|
{ ...baseDomain, https: true },
|
||||||
|
"web",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.middlewares).toContain("redirect-to-https");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Web entrypoint on https domain with redirect", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
{
|
||||||
|
...baseApp,
|
||||||
|
appName: "test",
|
||||||
|
redirects: [{ ...baseRedirect, uniqueConfigKey: 1 }],
|
||||||
|
},
|
||||||
|
{ ...baseDomain, https: true },
|
||||||
|
"web",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.middlewares).toContain("redirect-to-https");
|
||||||
|
expect(router.middlewares).not.toContain("redirect-test-1");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Websecure entrypoint on https domain", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
baseApp,
|
||||||
|
{ ...baseDomain, https: true },
|
||||||
|
"websecure",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Websecure entrypoint on https domain with redirect", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
{
|
||||||
|
...baseApp,
|
||||||
|
appName: "test",
|
||||||
|
redirects: [{ ...baseRedirect, uniqueConfigKey: 1 }],
|
||||||
|
},
|
||||||
|
{ ...baseDomain, https: true },
|
||||||
|
"websecure",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||||
|
expect(router.middlewares).toContain("redirect-test-1");
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Certificates */
|
||||||
|
|
||||||
|
test("CertificateType on websecure entrypoint", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
baseApp,
|
||||||
|
{ ...baseDomain, certificateType: "letsencrypt" },
|
||||||
|
"websecure",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.tls?.certResolver).toBe("letsencrypt");
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineConfig } from "vitest/config";
|
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
import tsconfigPaths from "vite-tsconfig-paths";
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
49
biome.json
49
biome.json
@@ -1,17 +1,34 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||||
"linter":{
|
"files": {
|
||||||
"rules": {
|
"ignore": ["node_modules/**", ".next/**", "drizzle/**", ".docker"]
|
||||||
"correctness":{
|
},
|
||||||
"useExhaustiveDependencies": "off"
|
"organizeImports": {
|
||||||
},
|
"enabled": true
|
||||||
"suspicious":{
|
},
|
||||||
"noArrayIndexKey": "off"
|
"linter": {
|
||||||
},
|
"rules": {
|
||||||
"a11y":{
|
"complexity": {
|
||||||
"noSvgWithoutTitle":"off"
|
"noUselessCatch": "off",
|
||||||
}
|
"noBannedTypes": "off"
|
||||||
}
|
},
|
||||||
}
|
"correctness": {
|
||||||
|
"useExhaustiveDependencies": "off",
|
||||||
}
|
"noUnsafeOptionalChaining": "off"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"noNonNullAssertion": "off"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noArrayIndexKey": "off",
|
||||||
|
"noExplicitAny": "off",
|
||||||
|
"noRedeclare": "off"
|
||||||
|
},
|
||||||
|
"a11y": {
|
||||||
|
"noSvgWithoutTitle": "off",
|
||||||
|
"useKeyWithClickEvents": "off",
|
||||||
|
"useAriaPropsForRole": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://ui.shadcn.com/schema.json",
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
"style": "default",
|
"style": "default",
|
||||||
"rsc": false,
|
"rsc": false,
|
||||||
"tsx": true,
|
"tsx": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "tailwind.config.ts",
|
"config": "tailwind.config.ts",
|
||||||
"css": "styles/globals.css",
|
"css": "styles/globals.css",
|
||||||
"baseColor": "zinc",
|
"baseColor": "zinc",
|
||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
"prefix": ""
|
"prefix": ""
|
||||||
},
|
},
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "@/components",
|
"components": "@/components",
|
||||||
"utils": "@/lib/utils"
|
"utils": "@/lib/utils"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,19 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
|
|
||||||
import { CardTitle } from "@/components/ui/card";
|
import { CardTitle } from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { AlertTriangle } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
import {
|
import {
|
||||||
InputOTP,
|
InputOTP,
|
||||||
InputOTPGroup,
|
InputOTPGroup,
|
||||||
InputOTPSlot,
|
InputOTPSlot,
|
||||||
} from "@/components/ui/input-otp";
|
} from "@/components/ui/input-otp";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { AlertTriangle } from "lucide-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const Login2FASchema = z.object({
|
const Login2FASchema = z.object({
|
||||||
pin: z.string().min(6, {
|
pin: z.string().min(6, {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -17,21 +19,19 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { HelpCircle, Settings } from "lucide-react";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { HelpCircle, Settings } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const HealthCheckSwarmSchema = z
|
const HealthCheckSwarmSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -6,8 +7,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { z } from "zod";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -16,11 +15,6 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { toast } from "sonner";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@@ -31,10 +25,16 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import Link from "next/link";
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Server } from "lucide-react";
|
import { Server } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import React from "react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AddSwarmSettings } from "./modify-swarm-settings";
|
import { AddSwarmSettings } from "./modify-swarm-settings";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -6,8 +6,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { z } from "zod";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -16,12 +14,14 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { toast } from "sonner";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import React from "react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -17,13 +18,6 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { PlusIcon } from "lucide-react";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -31,6 +25,12 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PlusIcon } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const AddPortSchema = z.object({
|
const AddPortSchema = z.object({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -8,10 +8,10 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Rss } from "lucide-react";
|
import { Rss } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { AddPort } from "./add-port";
|
import { AddPort } from "./add-port";
|
||||||
import { DeletePort } from "./delete-port";
|
import { DeletePort } from "./delete-port";
|
||||||
import { UpdatePort } from "./update-port";
|
import { UpdatePort } from "./update-port";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -17,14 +18,6 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -32,6 +25,13 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const UpdatePortSchema = z.object({
|
const UpdatePortSchema = z.object({
|
||||||
publishedPort: z.number().int().min(1).max(65535),
|
publishedPort: z.number().int().min(1).max(65535),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -11,22 +12,21 @@ import {
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormDescription,
|
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { PlusIcon } from "lucide-react";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
|
|
||||||
const AddRedirectchema = z.object({
|
const AddRedirectchema = z.object({
|
||||||
regex: z.string().min(1, "Regex required"),
|
regex: z.string().min(1, "Regex required"),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -8,6 +7,7 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Split } from "lucide-react";
|
import { Split } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { AddRedirect } from "./add-redirect";
|
import { AddRedirect } from "./add-redirect";
|
||||||
import { DeleteRedirect } from "./delete-redirect";
|
import { DeleteRedirect } from "./delete-redirect";
|
||||||
import { UpdateRedirect } from "./update-redirect";
|
import { UpdateRedirect } from "./update-redirect";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -11,22 +12,21 @@ import {
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormDescription,
|
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
const UpdateRedirectSchema = z.object({
|
const UpdateRedirectSchema = z.object({
|
||||||
regex: z.string().min(1, "Regex required"),
|
regex: z.string().min(1, "Regex required"),
|
||||||
permanent: z.boolean().default(false),
|
permanent: z.boolean().default(false),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -18,12 +19,11 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { PlusIcon } from "lucide-react";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const AddSecuritychema = z.object({
|
const AddSecuritychema = z.object({
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -8,6 +7,7 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { LockKeyhole } from "lucide-react";
|
import { LockKeyhole } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { AddSecurity } from "./add-security";
|
import { AddSecurity } from "./add-security";
|
||||||
import { DeleteSecurity } from "./delete-security";
|
import { DeleteSecurity } from "./delete-security";
|
||||||
import { UpdateSecurity } from "./update-security";
|
import { UpdateSecurity } from "./update-security";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -18,7 +19,6 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { PenBoxIcon, Pencil } from "lucide-react";
|
import { PenBoxIcon, Pencil } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -21,7 +22,6 @@ import React, { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
|
|
||||||
const addResourcesApplication = z.object({
|
const addResourcesApplication = z.object({
|
||||||
memoryReservation: z.number().nullable().optional(),
|
memoryReservation: z.number().nullable().optional(),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { File } from "lucide-react";
|
import { File } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { UpdateTraefikConfig } from "./update-traefik-config";
|
import { UpdateTraefikConfig } from "./update-traefik-config";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
|
|||||||
<CardTitle className="text-xl">Traefik</CardTitle>
|
<CardTitle className="text-xl">Traefik</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Modify the traefik config, in rare cases you may need to add
|
Modify the traefik config, in rare cases you may need to add
|
||||||
specific config, becarefull because modifying incorrectly can break
|
specific config, be careful because modifying incorrectly can break
|
||||||
traefik and your application
|
traefik and your application
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -17,14 +19,12 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import jsyaml from "js-yaml";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import jsyaml from "js-yaml";
|
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
|
||||||
|
|
||||||
const UpdateTraefikConfigSchema = z.object({
|
const UpdateTraefikConfigSchema = z.object({
|
||||||
traefikConfig: z.string(),
|
traefikConfig: z.string(),
|
||||||
@@ -110,12 +110,15 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(open) => {
|
<Dialog
|
||||||
setOpen(open)
|
open={open}
|
||||||
if (!open) {
|
onOpenChange={(open) => {
|
||||||
form.reset();
|
setOpen(open);
|
||||||
}
|
if (!open) {
|
||||||
}}>
|
form.reset();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button isLoading={isLoading}>Modify</Button>
|
<Button isLoading={isLoading}>Modify</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { Button } from "@/components/ui/button";
|
||||||
import type React from "react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { z } from "zod";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -22,12 +18,16 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { PlusIcon } from "lucide-react";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PlusIcon } from "lucide-react";
|
||||||
|
import type React from "react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
interface Props {
|
interface Props {
|
||||||
serviceId: string;
|
serviceId: string;
|
||||||
serviceType:
|
serviceType:
|
||||||
@@ -63,6 +63,7 @@ const mySchema = z.discriminatedUnion("type", [
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
type: z.literal("file"),
|
type: z.literal("file"),
|
||||||
|
filePath: z.string().min(1, "File path required"),
|
||||||
content: z.string().optional(),
|
content: z.string().optional(),
|
||||||
})
|
})
|
||||||
.merge(mountSchema),
|
.merge(mountSchema),
|
||||||
@@ -81,7 +82,7 @@ export const AddVolumes = ({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
type: serviceType === "compose" ? "file" : "bind",
|
type: serviceType === "compose" ? "file" : "bind",
|
||||||
hostPath: "",
|
hostPath: "",
|
||||||
mountPath: "",
|
mountPath: serviceType === "compose" ? "/" : "",
|
||||||
},
|
},
|
||||||
resolver: zodResolver(mySchema),
|
resolver: zodResolver(mySchema),
|
||||||
});
|
});
|
||||||
@@ -125,6 +126,7 @@ export const AddVolumes = ({
|
|||||||
serviceId,
|
serviceId,
|
||||||
content: data.content,
|
content: data.content,
|
||||||
mountPath: data.mountPath,
|
mountPath: data.mountPath,
|
||||||
|
filePath: data.filePath,
|
||||||
type: data.type,
|
type: data.type,
|
||||||
serviceType,
|
serviceType,
|
||||||
})
|
})
|
||||||
@@ -288,41 +290,62 @@ export const AddVolumes = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{type === "file" && (
|
{type === "file" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="content"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Content</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="Any content"
|
||||||
|
className="h-64"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="filePath"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>File Path</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Name of the file"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{serviceType !== "compose" && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="content"
|
name="mountPath"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Content</FormLabel>
|
<FormLabel>Mount Path (In the container)</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormControl>
|
<Input placeholder="Mount Path" {...field} />
|
||||||
<Textarea
|
|
||||||
placeholder="Any content"
|
|
||||||
className="h-64"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="mountPath"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Mount Path</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="Mount Path" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { TrashIcon } from "lucide-react";
|
import { TrashIcon } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -8,10 +8,10 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AlertTriangle, Package } from "lucide-react";
|
import { AlertTriangle, Package } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { AddVolumes } from "./add-volumes";
|
import { AddVolumes } from "./add-volumes";
|
||||||
import { DeleteVolume } from "./delete-volume";
|
import { DeleteVolume } from "./delete-volume";
|
||||||
import { UpdateVolume } from "./update-volume";
|
import { UpdateVolume } from "./update-volume";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
@@ -73,8 +73,7 @@ export const ShowVolumes = ({ applicationId }: Props) => {
|
|||||||
key={mount.mountId}
|
key={mount.mountId}
|
||||||
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
|
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
|
||||||
>
|
>
|
||||||
{/* <Package className="size-8 self-center text-muted-foreground" /> */}
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 flex-col gap-4 sm:gap-8">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
|
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="font-medium">Mount Type</span>
|
<span className="font-medium">Mount Type</span>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
@@ -91,12 +90,21 @@ export const ShowVolumes = ({ applicationId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{mount.type === "file" && (
|
{mount.type === "file" && (
|
||||||
<div className="flex flex-col gap-1">
|
<>
|
||||||
<span className="font-medium">Content</span>
|
<div className="flex flex-col gap-1">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="font-medium">Content</span>
|
||||||
{mount.content}
|
<span className="text-sm text-muted-foreground">
|
||||||
</span>
|
{mount.content}
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="font-medium">File Path</span>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{mount.filePath}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{mount.type === "bind" && (
|
{mount.type === "bind" && (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
@@ -118,6 +126,7 @@ export const ShowVolumes = ({ applicationId }: Props) => {
|
|||||||
mountId={mount.mountId}
|
mountId={mount.mountId}
|
||||||
type={mount.type}
|
type={mount.type}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
|
serviceType="application"
|
||||||
/>
|
/>
|
||||||
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
|
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -17,15 +18,14 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Pencil } from "lucide-react";
|
import { Pencil } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
|
|
||||||
const mountSchema = z.object({
|
const mountSchema = z.object({
|
||||||
mountPath: z.string().min(1, "Mount path required"),
|
mountPath: z.string().min(1, "Mount path required"),
|
||||||
@@ -48,6 +48,7 @@ const mySchema = z.discriminatedUnion("type", [
|
|||||||
.object({
|
.object({
|
||||||
type: z.literal("file"),
|
type: z.literal("file"),
|
||||||
content: z.string().optional(),
|
content: z.string().optional(),
|
||||||
|
filePath: z.string().min(1, "File path required"),
|
||||||
})
|
})
|
||||||
.merge(mountSchema),
|
.merge(mountSchema),
|
||||||
]);
|
]);
|
||||||
@@ -58,9 +59,23 @@ interface Props {
|
|||||||
mountId: string;
|
mountId: string;
|
||||||
type: "bind" | "volume" | "file";
|
type: "bind" | "volume" | "file";
|
||||||
refetch: () => void;
|
refetch: () => void;
|
||||||
|
serviceType:
|
||||||
|
| "application"
|
||||||
|
| "postgres"
|
||||||
|
| "redis"
|
||||||
|
| "mongo"
|
||||||
|
| "redis"
|
||||||
|
| "mysql"
|
||||||
|
| "mariadb"
|
||||||
|
| "compose";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
|
export const UpdateVolume = ({
|
||||||
|
mountId,
|
||||||
|
type,
|
||||||
|
refetch,
|
||||||
|
serviceType,
|
||||||
|
}: Props) => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { data } = api.mounts.one.useQuery(
|
const { data } = api.mounts.one.useQuery(
|
||||||
{
|
{
|
||||||
@@ -103,6 +118,7 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
|
|||||||
form.reset({
|
form.reset({
|
||||||
content: data.content || "",
|
content: data.content || "",
|
||||||
mountPath: data.mountPath,
|
mountPath: data.mountPath,
|
||||||
|
filePath: data.filePath || "",
|
||||||
type: "file",
|
type: "file",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -141,6 +157,7 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
|
|||||||
content: data.content,
|
content: data.content,
|
||||||
mountPath: data.mountPath,
|
mountPath: data.mountPath,
|
||||||
type: data.type,
|
type: data.type,
|
||||||
|
filePath: data.filePath,
|
||||||
mountId,
|
mountId,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -166,6 +183,11 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
|
|||||||
<DialogDescription>Update the mount</DialogDescription>
|
<DialogDescription>Update the mount</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
|
{type === "file" && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
Updating the mount will recreate the file or directory.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
@@ -211,40 +233,62 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{type === "file" && (
|
{type === "file" && (
|
||||||
<FormField
|
<>
|
||||||
control={form.control}
|
<FormField
|
||||||
name="content"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="content"
|
||||||
<FormItem>
|
render={({ field }) => (
|
||||||
<FormLabel>Content</FormLabel>
|
<FormItem>
|
||||||
<FormControl>
|
<FormLabel>Content</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea
|
<FormControl>
|
||||||
placeholder="Any content"
|
<Textarea
|
||||||
className="h-64"
|
placeholder="Any content"
|
||||||
|
className="h-64"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="filePath"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>File Path</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
disabled
|
||||||
|
placeholder="Name of the file"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{serviceType !== "compose" && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="mountPath"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Mount Path (In the container)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Mount Path" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="mountPath"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Mount Path</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="Mount Path" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,213 +1,213 @@
|
|||||||
import {
|
import { Button } from "@/components/ui/button";
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Cog } from "lucide-react";
|
import { Cog } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Input } from "@/components/ui/input";
|
import { z } from "zod";
|
||||||
|
|
||||||
enum BuildType {
|
enum BuildType {
|
||||||
dockerfile = "dockerfile",
|
dockerfile = "dockerfile",
|
||||||
heroku_buildpacks = "heroku_buildpacks",
|
heroku_buildpacks = "heroku_buildpacks",
|
||||||
paketo_buildpacks = "paketo_buildpacks",
|
paketo_buildpacks = "paketo_buildpacks",
|
||||||
nixpacks = "nixpacks",
|
nixpacks = "nixpacks",
|
||||||
}
|
}
|
||||||
|
|
||||||
const mySchema = z.discriminatedUnion("buildType", [
|
const mySchema = z.discriminatedUnion("buildType", [
|
||||||
z.object({
|
z.object({
|
||||||
buildType: z.literal("dockerfile"),
|
buildType: z.literal("dockerfile"),
|
||||||
dockerfile: z
|
dockerfile: z
|
||||||
.string({
|
.string({
|
||||||
required_error: "Dockerfile path is required",
|
required_error: "Dockerfile path is required",
|
||||||
invalid_type_error: "Dockerfile path is required",
|
invalid_type_error: "Dockerfile path is required",
|
||||||
})
|
})
|
||||||
.min(1, "Dockerfile required"),
|
.min(1, "Dockerfile required"),
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
buildType: z.literal("heroku_buildpacks"),
|
buildType: z.literal("heroku_buildpacks"),
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
buildType: z.literal("paketo_buildpacks"),
|
buildType: z.literal("paketo_buildpacks"),
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
buildType: z.literal("nixpacks"),
|
buildType: z.literal("nixpacks"),
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
type AddTemplate = z.infer<typeof mySchema>;
|
type AddTemplate = z.infer<typeof mySchema>;
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||||
const { mutateAsync, isLoading } =
|
const { mutateAsync, isLoading } =
|
||||||
api.application.saveBuildType.useMutation();
|
api.application.saveBuildType.useMutation();
|
||||||
const { data, refetch } = api.application.one.useQuery(
|
const { data, refetch } = api.application.one.useQuery(
|
||||||
{
|
{
|
||||||
applicationId,
|
applicationId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!applicationId,
|
enabled: !!applicationId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const form = useForm<AddTemplate>({
|
const form = useForm<AddTemplate>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
buildType: BuildType.nixpacks,
|
buildType: BuildType.nixpacks,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(mySchema),
|
resolver: zodResolver(mySchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildType = form.watch("buildType");
|
const buildType = form.watch("buildType");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
// TODO: refactor this
|
// TODO: refactor this
|
||||||
if (data.buildType === "dockerfile") {
|
if (data.buildType === "dockerfile") {
|
||||||
form.reset({
|
form.reset({
|
||||||
buildType: data.buildType,
|
buildType: data.buildType,
|
||||||
...(data.buildType && {
|
...(data.buildType && {
|
||||||
dockerfile: data.dockerfile || "",
|
dockerfile: data.dockerfile || "",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
form.reset({
|
form.reset({
|
||||||
buildType: data.buildType,
|
buildType: data.buildType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [form.formState.isSubmitSuccessful, form.reset, data, form]);
|
}, [form.formState.isSubmitSuccessful, form.reset, data, form]);
|
||||||
|
|
||||||
const onSubmit = async (data: AddTemplate) => {
|
const onSubmit = async (data: AddTemplate) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
applicationId,
|
applicationId,
|
||||||
buildType: data.buildType,
|
buildType: data.buildType,
|
||||||
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Build type saved");
|
toast.success("Build type saved");
|
||||||
await refetch();
|
await refetch();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error to save the build type");
|
toast.error("Error to save the build type");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="group relative w-full bg-transparent">
|
<Card className="group relative w-full bg-transparent">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-start justify-between">
|
<CardTitle className="flex items-start justify-between">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<span className="flex flex-col space-y-0.5">Build Type</span>
|
<span className="flex flex-col space-y-0.5">Build Type</span>
|
||||||
<p className="flex items-center text-sm font-normal text-muted-foreground">
|
<p className="flex items-center text-sm font-normal text-muted-foreground">
|
||||||
Select the way of building your code
|
Select the way of building your code
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden space-y-1 text-sm font-normal md:block">
|
<div className="hidden space-y-1 text-sm font-normal md:block">
|
||||||
<Cog className="size-6 text-muted-foreground" />
|
<Cog className="size-6 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="grid w-full gap-4 p-2"
|
className="grid w-full gap-4 p-2"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="buildType"
|
name="buildType"
|
||||||
defaultValue={form.control._defaultValues.buildType}
|
defaultValue={form.control._defaultValues.buildType}
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<FormItem className="space-y-3">
|
<FormItem className="space-y-3">
|
||||||
<FormLabel>Build Type</FormLabel>
|
<FormLabel>Build Type</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
className="flex flex-col space-y-1"
|
className="flex flex-col space-y-1"
|
||||||
>
|
>
|
||||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RadioGroupItem value="dockerfile" />
|
<RadioGroupItem value="dockerfile" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel className="font-normal">
|
<FormLabel className="font-normal">
|
||||||
Dockerfile
|
Dockerfile
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RadioGroupItem value="nixpacks" />
|
<RadioGroupItem value="nixpacks" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel className="font-normal">
|
<FormLabel className="font-normal">
|
||||||
Nixpacks
|
Nixpacks
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RadioGroupItem value="heroku_buildpacks" />
|
<RadioGroupItem value="heroku_buildpacks" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel className="font-normal">
|
<FormLabel className="font-normal">
|
||||||
Heroku Buildpacks
|
Heroku Buildpacks
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RadioGroupItem value="paketo_buildpacks" />
|
<RadioGroupItem value="paketo_buildpacks" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel className="font-normal">
|
<FormLabel className="font-normal">
|
||||||
Paketo Buildpacks
|
Paketo Buildpacks
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{buildType === "dockerfile" && (
|
{buildType === "dockerfile" && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="dockerfile"
|
name="dockerfile"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Docker File</FormLabel>
|
<FormLabel>Docker File</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder={"Path of your docker file"}
|
placeholder={"Path of your docker file"}
|
||||||
{...field}
|
{...field}
|
||||||
value={field.value ?? ""}
|
value={field.value ?? ""}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<Button isLoading={isLoading} type="submit">
|
<Button isLoading={isLoading} type="submit">
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -12,6 +11,7 @@ import {
|
|||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { RefreshCcw } from "lucide-react";
|
import { RefreshCcw } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -29,8 +29,8 @@ export const RefreshToken = ({ applicationId }: Props) => {
|
|||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
This action cannot be undone. This will permanently delete the
|
This action cannot be undone. This will change the refresh token and
|
||||||
domain
|
other tokens will be invalidated.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
|
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -10,10 +12,8 @@ import { api } from "@/utils/api";
|
|||||||
import { RocketIcon } from "lucide-react";
|
import { RocketIcon } from "lucide-react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { CancelQueues } from "./cancel-queues";
|
import { CancelQueues } from "./cancel-queues";
|
||||||
import { ShowDeployment } from "./show-deployment";
|
|
||||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
|
||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
|
||||||
import { RefreshToken } from "./refresh-token";
|
import { RefreshToken } from "./refresh-token";
|
||||||
|
import { ShowDeployment } from "./show-deployment";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -27,85 +28,103 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { useEffect, useState } from "react";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PlusIcon } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
// const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/;
|
import { domain } from "@/server/db/validations";
|
||||||
// .regex(hostnameRegex
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
const addDomain = z.object({
|
import type z from "zod";
|
||||||
host: z.string().min(1, "Hostname is required"),
|
|
||||||
path: z.string().min(1),
|
|
||||||
port: z.number(),
|
|
||||||
https: z.boolean(),
|
|
||||||
certificateType: z.enum(["letsencrypt", "none"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
type AddDomain = z.infer<typeof addDomain>;
|
type Domain = z.infer<typeof domain>;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
children?: React.ReactNode;
|
domainId?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddDomain = ({
|
export const AddDomain = ({
|
||||||
applicationId,
|
applicationId,
|
||||||
children = <PlusIcon className="h-4 w-4" />,
|
domainId = "",
|
||||||
|
children,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
const { data, refetch } = api.domain.one.useQuery(
|
||||||
const { mutateAsync, isError, error } = api.domain.create.useMutation();
|
{
|
||||||
|
domainId,
|
||||||
const form = useForm<AddDomain>({
|
|
||||||
defaultValues: {
|
|
||||||
host: "",
|
|
||||||
https: false,
|
|
||||||
path: "/",
|
|
||||||
port: 3000,
|
|
||||||
certificateType: "none",
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addDomain),
|
{
|
||||||
|
enabled: !!domainId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutateAsync, isError, error, isLoading } = domainId
|
||||||
|
? api.domain.update.useMutation()
|
||||||
|
: api.domain.create.useMutation();
|
||||||
|
|
||||||
|
const form = useForm<Domain>({
|
||||||
|
resolver: zodResolver(domain),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset();
|
if (data) {
|
||||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
form.reset({
|
||||||
|
...data,
|
||||||
|
/* Convert null to undefined */
|
||||||
|
path: data?.path || undefined,
|
||||||
|
port: data?.port || undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const onSubmit = async (data: AddDomain) => {
|
if (!domainId) {
|
||||||
|
form.reset({});
|
||||||
|
}
|
||||||
|
}, [form, form.reset, data, isLoading]);
|
||||||
|
|
||||||
|
const dictionary = {
|
||||||
|
success: domainId ? "Domain Updated" : "Domain Created",
|
||||||
|
error: domainId
|
||||||
|
? "Error to update the domain"
|
||||||
|
: "Error to create the domain",
|
||||||
|
submit: domainId ? "Update" : "Create",
|
||||||
|
dialogDescription: domainId
|
||||||
|
? "In this section you can edit a domain"
|
||||||
|
: "In this section you can add domains",
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (data: Domain) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
|
domainId,
|
||||||
applicationId,
|
applicationId,
|
||||||
host: data.host,
|
...data,
|
||||||
https: data.https,
|
|
||||||
path: data.path,
|
|
||||||
port: data.port,
|
|
||||||
certificateType: data.certificateType,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Domain Created");
|
toast.success(dictionary.success);
|
||||||
await utils.domain.byApplicationId.invalidate({
|
await utils.domain.byApplicationId.invalidate({
|
||||||
applicationId,
|
applicationId,
|
||||||
});
|
});
|
||||||
await utils.application.readTraefikConfig.invalidate({ applicationId });
|
await utils.application.readTraefikConfig.invalidate({ applicationId });
|
||||||
|
|
||||||
|
if (domainId) {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
setIsOpen(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error to create the domain");
|
toast.error(dictionary.error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
<Button>{children}</Button>
|
{children}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Domain</DialogTitle>
|
<DialogTitle>Domain</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
||||||
In this section you can add custom domains
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
|
|
||||||
@@ -169,33 +188,36 @@ export const AddDomain = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormField
|
{form.getValues().https && (
|
||||||
control={form.control}
|
<FormField
|
||||||
name="certificateType"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="certificateType"
|
||||||
<FormItem className="col-span-2">
|
render={({ field }) => (
|
||||||
<FormLabel>Certificate</FormLabel>
|
<FormItem className="col-span-2">
|
||||||
<Select
|
<FormLabel>Certificate</FormLabel>
|
||||||
onValueChange={field.onChange}
|
<Select
|
||||||
defaultValue={field.value || ""}
|
onValueChange={field.onChange}
|
||||||
>
|
defaultValue={field.value || ""}
|
||||||
<FormControl>
|
>
|
||||||
<SelectTrigger>
|
<FormControl>
|
||||||
<SelectValue placeholder="Select a certificate" />
|
<SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="Select a certificate" />
|
||||||
</FormControl>
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="none">None</SelectItem>
|
||||||
|
<SelectItem value={"letsencrypt"}>
|
||||||
|
Letsencrypt (Default)
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="none">None</SelectItem>
|
|
||||||
<SelectItem value={"letsencrypt"}>
|
|
||||||
Letsencrypt (Default)
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="https"
|
name="https"
|
||||||
@@ -206,6 +228,7 @@ export const AddDomain = ({
|
|||||||
<FormDescription>
|
<FormDescription>
|
||||||
Automatically provision SSL Certificate.
|
Automatically provision SSL Certificate.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -226,7 +249,7 @@ export const AddDomain = ({
|
|||||||
form="hook-form"
|
form="hook-form"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Create
|
{dictionary.submit}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { TrashIcon } from "lucide-react";
|
import { TrashIcon } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
import { RefreshCcw } from "lucide-react";
|
import { RefreshCcw } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
import { GenerateTraefikMe } from "./generate-traefikme";
|
import { GenerateTraefikMe } from "./generate-traefikme";
|
||||||
import { GenerateWildCard } from "./generate-wildcard";
|
import { GenerateWildCard } from "./generate-wildcard";
|
||||||
import Link from "next/link";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { RefreshCcw } from "lucide-react";
|
import { RefreshCcw } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { SquareAsterisk } from "lucide-react";
|
import { SquareAsterisk } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -6,14 +6,12 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { ExternalLink, GlobeIcon, RefreshCcw } from "lucide-react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { DeleteDomain } from "./delete-domain";
|
import { api } from "@/utils/api";
|
||||||
|
import { ExternalLink, GlobeIcon, PenBoxIcon } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { AddDomain } from "./add-domain";
|
import { AddDomain } from "./add-domain";
|
||||||
import { UpdateDomain } from "./update-domain";
|
import { DeleteDomain } from "./delete-domain";
|
||||||
import { GenerateDomain } from "./generate-domain";
|
import { GenerateDomain } from "./generate-domain";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -43,7 +41,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
|||||||
<div className="flex flex-row gap-4 flex-wrap">
|
<div className="flex flex-row gap-4 flex-wrap">
|
||||||
{data && data?.length > 0 && (
|
{data && data?.length > 0 && (
|
||||||
<AddDomain applicationId={applicationId}>
|
<AddDomain applicationId={applicationId}>
|
||||||
<GlobeIcon className="size-4" /> Add Domain
|
<Button>
|
||||||
|
<GlobeIcon className="size-4" /> Add Domain
|
||||||
|
</Button>
|
||||||
</AddDomain>
|
</AddDomain>
|
||||||
)}
|
)}
|
||||||
{data && data?.length > 0 && (
|
{data && data?.length > 0 && (
|
||||||
@@ -61,7 +61,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
<div className="flex flex-row gap-4 flex-wrap">
|
<div className="flex flex-row gap-4 flex-wrap">
|
||||||
<AddDomain applicationId={applicationId}>
|
<AddDomain applicationId={applicationId}>
|
||||||
<GlobeIcon className="size-4" /> Add Domain
|
<Button>
|
||||||
|
<GlobeIcon className="size-4" /> Add Domain
|
||||||
|
</Button>
|
||||||
</AddDomain>
|
</AddDomain>
|
||||||
|
|
||||||
<GenerateDomain applicationId={applicationId} />
|
<GenerateDomain applicationId={applicationId} />
|
||||||
@@ -90,7 +92,14 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
|||||||
{item.https ? "HTTPS" : "HTTP"}
|
{item.https ? "HTTPS" : "HTTP"}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex flex-row gap-1">
|
<div className="flex flex-row gap-1">
|
||||||
<UpdateDomain domainId={item.domainId} />
|
<AddDomain
|
||||||
|
applicationId={applicationId}
|
||||||
|
domainId={item.domainId}
|
||||||
|
>
|
||||||
|
<Button variant="ghost">
|
||||||
|
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</AddDomain>
|
||||||
<DeleteDomain domainId={item.domainId} />
|
<DeleteDomain domainId={item.domainId} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,254 +0,0 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/;
|
|
||||||
|
|
||||||
const updateDomain = z.object({
|
|
||||||
host: z.string().regex(hostnameRegex, { message: "Invalid hostname" }),
|
|
||||||
path: z.string().min(1),
|
|
||||||
port: z
|
|
||||||
.number()
|
|
||||||
.min(1, { message: "Port must be at least 1" })
|
|
||||||
.max(65535, { message: "Port must be 65535 or below" }),
|
|
||||||
https: z.boolean(),
|
|
||||||
certificateType: z.enum(["letsencrypt", "none"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
type UpdateDomain = z.infer<typeof updateDomain>;
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
domainId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UpdateDomain = ({ domainId }: Props) => {
|
|
||||||
const utils = api.useUtils();
|
|
||||||
const { data, refetch } = api.domain.one.useQuery(
|
|
||||||
{
|
|
||||||
domainId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: !!domainId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const { mutateAsync, isError, error } = api.domain.update.useMutation();
|
|
||||||
|
|
||||||
const form = useForm<UpdateDomain>({
|
|
||||||
defaultValues: {
|
|
||||||
host: "",
|
|
||||||
https: true,
|
|
||||||
path: "/",
|
|
||||||
port: 3000,
|
|
||||||
certificateType: "none",
|
|
||||||
},
|
|
||||||
resolver: zodResolver(updateDomain),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
form.reset({
|
|
||||||
host: data.host || "",
|
|
||||||
port: data.port || 3000,
|
|
||||||
path: data.path || "/",
|
|
||||||
https: data.https,
|
|
||||||
certificateType: data.certificateType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [form, form.reset, data]);
|
|
||||||
|
|
||||||
const onSubmit = async (data: UpdateDomain) => {
|
|
||||||
await mutateAsync({
|
|
||||||
domainId,
|
|
||||||
host: data.host,
|
|
||||||
https: data.https,
|
|
||||||
path: data.path,
|
|
||||||
port: data.port,
|
|
||||||
certificateType: data.certificateType,
|
|
||||||
})
|
|
||||||
.then(async (data) => {
|
|
||||||
toast.success("Domain Updated");
|
|
||||||
await refetch();
|
|
||||||
await utils.domain.byApplicationId.invalidate({
|
|
||||||
applicationId: data?.applicationId,
|
|
||||||
});
|
|
||||||
await utils.application.readTraefikConfig.invalidate({
|
|
||||||
applicationId: data?.applicationId,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error("Error to update the domain");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger className="" asChild>
|
|
||||||
<Button variant="ghost">
|
|
||||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Domain</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
In this section you can add custom domains
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
|
||||||
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
id="hook-form"
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="grid w-full gap-8 "
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="host"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Host</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="api.dokploy.com" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="path"
|
|
||||||
render={({ field }) => {
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Path</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder={"/"} {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="port"
|
|
||||||
render={({ field }) => {
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Container Port</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder={"3000"}
|
|
||||||
{...field}
|
|
||||||
onChange={(e) => {
|
|
||||||
field.onChange(Number.parseInt(e.target.value));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="certificateType"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="col-span-2">
|
|
||||||
<FormLabel>Certificate</FormLabel>
|
|
||||||
<Select
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
value={field.value}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select a certificate" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value={"none"}>None</SelectItem>
|
|
||||||
<SelectItem value={"letsencrypt"}>
|
|
||||||
Letsencrypt
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="https"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel>HTTPS</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Automatically provision SSL Certificate.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
isLoading={form.formState.isSubmitting}
|
|
||||||
form="hook-form"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</Form>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,30 +1,16 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { z } from "zod";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { useForm } from "react-hook-form";
|
import { Form } from "@/components/ui/form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { Secrets } from "@/components/ui/secrets";
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
import { z } from "zod";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
|
||||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
|
||||||
|
|
||||||
const addEnvironmentSchema = z.object({
|
const addEnvironmentSchema = z.object({
|
||||||
environment: z.string(),
|
env: z.string(),
|
||||||
|
buildArgs: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
|
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
|
||||||
@@ -34,7 +20,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ShowEnvironment = ({ applicationId }: Props) => {
|
export const ShowEnvironment = ({ applicationId }: Props) => {
|
||||||
const [isEnvVisible, setIsEnvVisible] = useState(true);
|
|
||||||
const { mutateAsync, isLoading } =
|
const { mutateAsync, isLoading } =
|
||||||
api.application.saveEnvironment.useMutation();
|
api.application.saveEnvironment.useMutation();
|
||||||
|
|
||||||
@@ -46,24 +31,19 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
enabled: !!applicationId,
|
enabled: !!applicationId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const form = useForm<EnvironmentSchema>({
|
const form = useForm<EnvironmentSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
environment: "",
|
env: data?.env || "",
|
||||||
|
buildArgs: data?.buildArgs || "",
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addEnvironmentSchema),
|
resolver: zodResolver(addEnvironmentSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
form.reset({
|
|
||||||
environment: data.env || "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [form.reset, data, form]);
|
|
||||||
|
|
||||||
const onSubmit = async (data: EnvironmentSchema) => {
|
const onSubmit = async (data: EnvironmentSchema) => {
|
||||||
mutateAsync({
|
mutateAsync({
|
||||||
env: data.environment,
|
env: data.env,
|
||||||
|
buildArgs: data.buildArgs,
|
||||||
applicationId,
|
applicationId,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
@@ -74,94 +54,50 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
toast.error("Error to add environment");
|
toast.error("Error to add environment");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
|
||||||
if (isEnvVisible) {
|
|
||||||
if (data?.env) {
|
|
||||||
const maskedLines = data.env
|
|
||||||
.split("\n")
|
|
||||||
.map((line) => "*".repeat(line.length))
|
|
||||||
.join("\n");
|
|
||||||
form.reset({
|
|
||||||
environment: maskedLines,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
form.reset({
|
|
||||||
environment: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
form.reset({
|
|
||||||
environment: data?.env || "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [form.reset, data, form, isEnvVisible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col gap-5 ">
|
<Form {...form}>
|
||||||
<Card className="bg-background">
|
<form
|
||||||
<CardHeader className="flex flex-row w-full items-center justify-between">
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
<div>
|
className="flex w-full flex-col gap-5 "
|
||||||
<CardTitle className="text-xl">Environment Settings</CardTitle>
|
>
|
||||||
<CardDescription>
|
<Card className="bg-background">
|
||||||
You can add environment variables to your resource.
|
<Secrets
|
||||||
</CardDescription>
|
name="env"
|
||||||
</div>
|
title="Environment Settings"
|
||||||
|
description="You can add environment variables to your resource."
|
||||||
<Toggle
|
placeholder={["NODE_ENV=production", "PORT=3000"].join("\n")}
|
||||||
aria-label="Toggle bold"
|
/>
|
||||||
pressed={isEnvVisible}
|
{data?.buildType === "dockerfile" && (
|
||||||
onPressedChange={setIsEnvVisible}
|
<Secrets
|
||||||
>
|
name="buildArgs"
|
||||||
{isEnvVisible ? (
|
title="Build-time Variables"
|
||||||
<EyeOffIcon className="h-4 w-4 text-muted-foreground" />
|
description={
|
||||||
) : (
|
<span>
|
||||||
<EyeIcon className="h-4 w-4 text-muted-foreground" />
|
Available only at build-time. See documentation
|
||||||
)}
|
<a
|
||||||
</Toggle>
|
className="text-primary"
|
||||||
</CardHeader>
|
href="https://docs.docker.com/build/guide/build-args/"
|
||||||
<CardContent>
|
target="_blank"
|
||||||
<Form {...form}>
|
rel="noopener noreferrer"
|
||||||
<form
|
>
|
||||||
id="hook-form"
|
here
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
</a>
|
||||||
className="w-full space-y-4"
|
.
|
||||||
>
|
</span>
|
||||||
<FormField
|
}
|
||||||
control={form.control}
|
placeholder="NPM_TOKEN=xyz"
|
||||||
name="environment"
|
/>
|
||||||
render={({ field }) => (
|
)}
|
||||||
<FormItem className="w-full">
|
<CardContent>
|
||||||
<FormControl>
|
<div className="flex flex-row justify-end">
|
||||||
<CodeEditor
|
<Button isLoading={isLoading} className="w-fit" type="submit">
|
||||||
language="properties"
|
Save
|
||||||
disabled={isEnvVisible}
|
</Button>
|
||||||
placeholder={`NODE_ENV=production
|
</div>
|
||||||
PORT=3000
|
</CardContent>
|
||||||
`}
|
</Card>
|
||||||
className="h-96 font-mono"
|
</form>
|
||||||
{...field}
|
</Form>
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex flex-row justify-end">
|
|
||||||
<Button
|
|
||||||
disabled={isEnvVisible}
|
|
||||||
isLoading={isLoading}
|
|
||||||
className="w-fit"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -11,9 +7,13 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { useEffect } from "react";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const DockerProviderSchema = z.object({
|
const DockerProviderSchema = z.object({
|
||||||
dockerImage: z.string().min(1, {
|
dockerImage: z.string().min(1, {
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Dropzone } from "@/components/ui/dropzone";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { type UploadFile, uploadFileSchema } from "@/utils/schema";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { TrashIcon } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
applicationId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SaveDragNDrop = ({ applicationId }: Props) => {
|
||||||
|
const { data, refetch } = api.application.one.useQuery({ applicationId });
|
||||||
|
|
||||||
|
const { mutateAsync, isLoading } =
|
||||||
|
api.application.dropDeployment.useMutation();
|
||||||
|
|
||||||
|
const form = useForm<UploadFile>({
|
||||||
|
defaultValues: {},
|
||||||
|
resolver: zodResolver(uploadFileSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
form.reset({
|
||||||
|
dropBuildPath: data.dropBuildPath || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [data, form, form.reset, form.formState.isSubmitSuccessful]);
|
||||||
|
const zip = form.watch("zip");
|
||||||
|
|
||||||
|
const onSubmit = async (values: UploadFile) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
formData.append("zip", values.zip);
|
||||||
|
formData.append("applicationId", applicationId);
|
||||||
|
if (values.dropBuildPath) {
|
||||||
|
formData.append("dropBuildPath", values.dropBuildPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
await mutateAsync(formData)
|
||||||
|
.then(async () => {
|
||||||
|
toast.success("Deployment saved");
|
||||||
|
await refetch();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error to save the deployment");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="flex flex-col gap-4"
|
||||||
|
>
|
||||||
|
<div className="grid md:grid-cols-2 gap-4 ">
|
||||||
|
<div className="md:col-span-2 space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="dropBuildPath"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="w-full ">
|
||||||
|
<FormLabel>Build Path</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} placeholder="Build Path" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="zip"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="w-full ">
|
||||||
|
<FormLabel>Zip file</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Dropzone
|
||||||
|
{...field}
|
||||||
|
dropMessage="Drop files or click here"
|
||||||
|
accept=".zip"
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e instanceof FileList) {
|
||||||
|
field.onChange(e[0]);
|
||||||
|
} else {
|
||||||
|
field.onChange(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
{zip instanceof File && (
|
||||||
|
<div className="flex flex-row gap-4 items-center">
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{zip.name} ({zip.size} bytes)
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className="w-fit"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => {
|
||||||
|
field.onChange(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TrashIcon className="w-4 h-4 text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row justify-end">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-fit"
|
||||||
|
isLoading={isLoading}
|
||||||
|
disabled={!zip}
|
||||||
|
>
|
||||||
|
Deploy{" "}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,13 +1,4 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -17,11 +8,20 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import copy from "copy-to-clipboard";
|
import { KeyRoundIcon, LockIcon } from "lucide-react";
|
||||||
import { CopyIcon, LockIcon } from "lucide-react";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -33,6 +33,7 @@ const GitProviderSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
branch: z.string().min(1, "Branch required"),
|
branch: z.string().min(1, "Branch required"),
|
||||||
buildPath: z.string().min(1, "Build Path required"),
|
buildPath: z.string().min(1, "Build Path required"),
|
||||||
|
sshKey: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type GitProvider = z.infer<typeof GitProviderSchema>;
|
type GitProvider = z.infer<typeof GitProviderSchema>;
|
||||||
@@ -43,19 +44,18 @@ interface Props {
|
|||||||
|
|
||||||
export const SaveGitProvider = ({ applicationId }: Props) => {
|
export const SaveGitProvider = ({ applicationId }: Props) => {
|
||||||
const { data, refetch } = api.application.one.useQuery({ applicationId });
|
const { data, refetch } = api.application.one.useQuery({ applicationId });
|
||||||
|
const { data: sshKeys } = api.sshKey.all.useQuery();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const { mutateAsync, isLoading } =
|
const { mutateAsync, isLoading } =
|
||||||
api.application.saveGitProdiver.useMutation();
|
api.application.saveGitProdiver.useMutation();
|
||||||
const { mutateAsync: generateSSHKey, isLoading: isGeneratingSSHKey } =
|
|
||||||
api.application.generateSSHKey.useMutation();
|
|
||||||
|
|
||||||
const { mutateAsync: removeSSHKey, isLoading: isRemovingSSHKey } =
|
|
||||||
api.application.removeSSHKey.useMutation();
|
|
||||||
const form = useForm<GitProvider>({
|
const form = useForm<GitProvider>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
branch: "",
|
branch: "",
|
||||||
buildPath: "/",
|
buildPath: "/",
|
||||||
repositoryURL: "",
|
repositoryURL: "",
|
||||||
|
sshKey: undefined,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GitProviderSchema),
|
resolver: zodResolver(GitProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -63,6 +63,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
|
sshKey: data.customGitSSHKeyId || undefined,
|
||||||
branch: data.customGitBranch || "",
|
branch: data.customGitBranch || "",
|
||||||
buildPath: data.customGitBuildPath || "/",
|
buildPath: data.customGitBuildPath || "/",
|
||||||
repositoryURL: data.customGitUrl || "",
|
repositoryURL: data.customGitUrl || "",
|
||||||
@@ -75,6 +76,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
customGitBranch: values.branch,
|
customGitBranch: values.branch,
|
||||||
customGitBuildPath: values.buildPath,
|
customGitBuildPath: values.buildPath,
|
||||||
customGitUrl: values.repositoryURL,
|
customGitUrl: values.repositoryURL,
|
||||||
|
customGitSSHKeyId: values.sshKey === "none" ? null : values.sshKey,
|
||||||
applicationId,
|
applicationId,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
@@ -92,160 +94,103 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="flex flex-col gap-4"
|
className="flex flex-col gap-4"
|
||||||
>
|
>
|
||||||
<div className="grid md:grid-cols-2 gap-4 ">
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
<div className="md:col-span-2 space-y-4">
|
<div className="flex items-end col-span-2 gap-4">
|
||||||
<FormField
|
<div className="grow">
|
||||||
control={form.control}
|
<FormField
|
||||||
name="repositoryURL"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="repositoryURL"
|
||||||
<FormItem>
|
render={({ field }) => (
|
||||||
<FormLabel className="flex flex-row justify-between">
|
<FormItem>
|
||||||
Repository URL
|
<FormLabel>Repository URL</FormLabel>
|
||||||
<div className="flex gap-2">
|
<FormControl>
|
||||||
<Dialog>
|
<Input placeholder="git@bitbucket.org" {...field} />
|
||||||
<DialogTrigger className="flex flex-row gap-2">
|
</FormControl>
|
||||||
<LockIcon className="size-4 text-muted-foreground" />?
|
<FormMessage />
|
||||||
</DialogTrigger>
|
</FormItem>
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
)}
|
||||||
<DialogHeader>
|
/>
|
||||||
<DialogTitle>Private Repository</DialogTitle>
|
</div>
|
||||||
<DialogDescription>
|
{sshKeys && sshKeys.length > 0 ? (
|
||||||
If your repository is private is necessary to
|
<FormField
|
||||||
generate SSH Keys to add to your git provider.
|
control={form.control}
|
||||||
</DialogDescription>
|
name="sshKey"
|
||||||
</DialogHeader>
|
render={({ field }) => (
|
||||||
<div className="grid gap-4 py-4">
|
<FormItem className="basis-40">
|
||||||
<div className="relative">
|
<FormLabel className="w-full inline-flex justify-between">
|
||||||
<Textarea
|
SSH Key
|
||||||
placeholder="Please click on Generate SSH Key"
|
<LockIcon className="size-4 text-muted-foreground" />
|
||||||
className="no-scrollbar h-64 text-muted-foreground"
|
</FormLabel>
|
||||||
disabled={!data?.customGitSSHKey}
|
<FormControl>
|
||||||
contentEditable={false}
|
<Select
|
||||||
value={
|
key={field.value}
|
||||||
data?.customGitSSHKey ||
|
onValueChange={field.onChange}
|
||||||
"Please click on Generate SSH Key"
|
defaultValue={field.value}
|
||||||
}
|
value={field.value}
|
||||||
/>
|
>
|
||||||
<button
|
<SelectTrigger>
|
||||||
type="button"
|
<SelectValue placeholder="Select a key" />
|
||||||
className="absolute right-2 top-2"
|
</SelectTrigger>
|
||||||
onClick={() => {
|
<SelectContent>
|
||||||
copy(
|
<SelectGroup>
|
||||||
data?.customGitSSHKey ||
|
{sshKeys?.map((sshKey) => (
|
||||||
"Generate a SSH Key",
|
<SelectItem
|
||||||
);
|
key={sshKey.sshKeyId}
|
||||||
toast.success("SSH Copied to clipboard");
|
value={sshKey.sshKeyId}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<CopyIcon className="size-4" />
|
{sshKey.name}
|
||||||
</button>
|
</SelectItem>
|
||||||
</div>
|
))}
|
||||||
</div>
|
<SelectItem value="none">None</SelectItem>
|
||||||
<DialogFooter className="flex sm:justify-between gap-3.5 flex-col sm:flex-col w-full">
|
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
|
||||||
<div className="flex flex-row gap-2 w-full justify-between flex-wrap">
|
</SelectGroup>
|
||||||
{data?.customGitSSHKey && (
|
</SelectContent>
|
||||||
<Button
|
</Select>
|
||||||
variant="destructive"
|
</FormControl>
|
||||||
isLoading={
|
</FormItem>
|
||||||
isGeneratingSSHKey || isRemovingSSHKey
|
)}
|
||||||
}
|
/>
|
||||||
className="max-sm:w-full"
|
) : (
|
||||||
onClick={async () => {
|
<Button
|
||||||
await removeSSHKey({
|
variant="secondary"
|
||||||
applicationId,
|
onClick={() => router.push("/dashboard/settings/ssh-keys")}
|
||||||
})
|
type="button"
|
||||||
.then(async () => {
|
>
|
||||||
toast.success("SSH Key Removed");
|
<KeyRoundIcon className="size-4" /> Add SSH Key
|
||||||
await refetch();
|
</Button>
|
||||||
})
|
)}
|
||||||
.catch(() => {
|
|
||||||
toast.error(
|
|
||||||
"Error to remove the SSH Key",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Remove SSH Key
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
isLoading={
|
|
||||||
isGeneratingSSHKey || isRemovingSSHKey
|
|
||||||
}
|
|
||||||
className="max-sm:w-full"
|
|
||||||
onClick={async () => {
|
|
||||||
await generateSSHKey({
|
|
||||||
applicationId,
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
toast.success("SSH Key Generated");
|
|
||||||
await refetch();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error(
|
|
||||||
"Error to generate the SSH Key",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Generate SSH Key
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
Is recommended to remove the SSH Key if you want
|
|
||||||
to deploy a public repository.
|
|
||||||
</span>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="git@bitbucket.org" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="branch"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Branch</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="Branch" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="buildPath"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Build Path</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="/" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="branch"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Branch</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Branch" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="buildPath"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Build Path</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="/" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row justify-end">
|
<div className="flex flex-row justify-end">
|
||||||
<Button type="submit" className="w-fit" isLoading={isLoading}>
|
<Button type="submit" className="w-fit" isLoading={isLoading}>
|
||||||
Save{" "}
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import { api } from "@/utils/api";
|
|||||||
import { GitBranch, LockIcon } from "lucide-react";
|
import { GitBranch, LockIcon } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { SaveDragNDrop } from "./save-drag-n-drop";
|
||||||
|
|
||||||
type TabState = "github" | "docker" | "git";
|
type TabState = "github" | "docker" | "git" | "drop";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
@@ -62,6 +63,12 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
|
|||||||
>
|
>
|
||||||
Git
|
Git
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value="drop"
|
||||||
|
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||||
|
>
|
||||||
|
Drop
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="github" className="w-full p-2">
|
<TabsContent value="github" className="w-full p-2">
|
||||||
{haveGithubConfigured ? (
|
{haveGithubConfigured ? (
|
||||||
@@ -89,6 +96,9 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
|
|||||||
<TabsContent value="git" className="w-full p-2">
|
<TabsContent value="git" className="w-full p-2">
|
||||||
<SaveGitProvider applicationId={applicationId} />
|
<SaveGitProvider applicationId={applicationId} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
<TabsContent value="drop" className="w-full p-2">
|
||||||
|
<SaveDragNDrop applicationId={applicationId} />
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import { CheckCircle2, Terminal } from "lucide-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||||
import { RedbuildApplication } from "../rebuild-application";
|
import { RedbuildApplication } from "../rebuild-application";
|
||||||
import { StartApplication } from "../start-application";
|
import { StartApplication } from "../start-application";
|
||||||
import { StopApplication } from "../stop-application";
|
import { StopApplication } from "../stop-application";
|
||||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
|
||||||
import { CheckCircle2, Terminal } from "lucide-react";
|
|
||||||
import { DeployApplication } from "./deploy-application";
|
import { DeployApplication } from "./deploy-application";
|
||||||
import { ResetApplication } from "./reset-application";
|
import { ResetApplication } from "./reset-application";
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import dynamic from "next/dynamic";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -6,6 +5,7 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -16,8 +16,8 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
export const DockerLogs = dynamic(
|
export const DockerLogs = dynamic(
|
||||||
() =>
|
() =>
|
||||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -7,7 +9,6 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -16,16 +17,15 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { AlertTriangle, SquarePen } from "lucide-react";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { AlertTriangle, SquarePen } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const updateApplicationSchema = z.object({
|
const updateApplicationSchema = z.object({
|
||||||
name: z.string().min(1, {
|
name: z.string().min(1, {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -6,8 +6,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { z } from "zod";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -17,12 +15,14 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { toast } from "sonner";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import React from "react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -8,10 +8,10 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Package } from "lucide-react";
|
import { Package } from "lucide-react";
|
||||||
import { DeleteVolume } from "../../application/advanced/volumes/delete-volume";
|
import React from "react";
|
||||||
import { AddVolumes } from "../../application/advanced/volumes/add-volumes";
|
import { AddVolumes } from "../../application/advanced/volumes/add-volumes";
|
||||||
|
import { DeleteVolume } from "../../application/advanced/volumes/delete-volume";
|
||||||
import { UpdateVolume } from "../../application/advanced/volumes/update-volume";
|
import { UpdateVolume } from "../../application/advanced/volumes/update-volume";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
|
|||||||
key={mount.mountId}
|
key={mount.mountId}
|
||||||
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
|
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 flex-col gap-4 sm:gap-8">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="font-medium">Mount Type</span>
|
<span className="font-medium">Mount Type</span>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
@@ -91,12 +91,20 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{mount.type === "file" && (
|
{mount.type === "file" && (
|
||||||
<div className="flex flex-col gap-1">
|
<>
|
||||||
<span className="font-medium">Content</span>
|
<div className="flex flex-col gap-1">
|
||||||
<span className="text-sm text-muted-foreground w-40 truncate">
|
<span className="font-medium">Content</span>
|
||||||
{mount.content}
|
<span className="text-sm text-muted-foreground w-40 truncate">
|
||||||
</span>
|
{mount.content}
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="font-medium">File Path</span>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{mount.filePath}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{mount.type === "bind" && (
|
{mount.type === "bind" && (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
@@ -118,6 +126,7 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
|
|||||||
mountId={mount.mountId}
|
mountId={mount.mountId}
|
||||||
type={mount.type}
|
type={mount.type}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
|
serviceType="compose"
|
||||||
/>
|
/>
|
||||||
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
|
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -12,6 +11,7 @@ import {
|
|||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { RefreshCcw } from "lucide-react";
|
import { RefreshCcw } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
|
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -9,11 +11,9 @@ import {
|
|||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { RocketIcon } from "lucide-react";
|
import { RocketIcon } from "lucide-react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
|
||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
|
||||||
import { ShowDeploymentCompose } from "./show-deployment-compose";
|
|
||||||
import { RefreshTokenCompose } from "./refresh-token-compose";
|
|
||||||
import { CancelQueuesCompose } from "./cancel-queues-compose";
|
import { CancelQueuesCompose } from "./cancel-queues-compose";
|
||||||
|
import { RefreshTokenCompose } from "./refresh-token-compose";
|
||||||
|
import { ShowDeploymentCompose } from "./show-deployment-compose";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -6,10 +7,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -17,11 +14,14 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const addEnvironmentSchema = z.object({
|
const addEnvironmentSchema = z.object({
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ExternalLink, Globe, Terminal } from "lucide-react";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
|
||||||
import { RedbuildCompose } from "./rebuild-compose";
|
|
||||||
import { DeployCompose } from "./deploy-compose";
|
|
||||||
import { StopCompose } from "./stop-compose";
|
|
||||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -16,7 +8,15 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { CheckCircle2, ExternalLink, Globe, Terminal } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||||
|
import { DeployCompose } from "./deploy-compose";
|
||||||
|
import { RedbuildCompose } from "./rebuild-compose";
|
||||||
|
import { StopCompose } from "./stop-compose";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
@@ -50,7 +50,6 @@ export const ComposeActions = ({ composeId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-row gap-4 w-full flex-wrap ">
|
<div className="flex flex-row gap-4 w-full flex-wrap ">
|
||||||
<DeployCompose composeId={composeId} />
|
<DeployCompose composeId={composeId} />
|
||||||
|
|
||||||
<Toggle
|
<Toggle
|
||||||
aria-label="Toggle italic"
|
aria-label="Toggle italic"
|
||||||
pressed={data?.autoDeploy || false}
|
pressed={data?.autoDeploy || false}
|
||||||
@@ -67,8 +66,9 @@ export const ComposeActions = ({ composeId }: Props) => {
|
|||||||
toast.error("Error to update Auto Deploy");
|
toast.error("Error to update Auto Deploy");
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
className="flex flex-row gap-2 items-center"
|
||||||
>
|
>
|
||||||
Autodeploy
|
Autodeploy {data?.autoDeploy && <CheckCircle2 className="size-4" />}
|
||||||
</Toggle>
|
</Toggle>
|
||||||
<RedbuildCompose composeId={composeId} />
|
<RedbuildCompose composeId={composeId} />
|
||||||
{data?.composeType === "docker-compose" && (
|
{data?.composeType === "docker-compose" && (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { api } from "@/utils/api";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { useEffect } from "react";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -7,14 +7,14 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
|
import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
|
||||||
import { toast } from "sonner";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { RandomizeCompose } from "./randomize-compose";
|
import { RandomizeCompose } from "./randomize-compose";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -17,11 +8,19 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import copy from "copy-to-clipboard";
|
import { KeyRoundIcon, LockIcon } from "lucide-react";
|
||||||
import { CopyIcon, LockIcon } from "lucide-react";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -33,6 +32,7 @@ const GitProviderSchema = z.object({
|
|||||||
message: "Repository URL is required",
|
message: "Repository URL is required",
|
||||||
}),
|
}),
|
||||||
branch: z.string().min(1, "Branch required"),
|
branch: z.string().min(1, "Branch required"),
|
||||||
|
sshKey: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type GitProvider = z.infer<typeof GitProviderSchema>;
|
type GitProvider = z.infer<typeof GitProviderSchema>;
|
||||||
@@ -43,19 +43,17 @@ interface Props {
|
|||||||
|
|
||||||
export const SaveGitProviderCompose = ({ composeId }: Props) => {
|
export const SaveGitProviderCompose = ({ composeId }: Props) => {
|
||||||
const { data, refetch } = api.compose.one.useQuery({ composeId });
|
const { data, refetch } = api.compose.one.useQuery({ composeId });
|
||||||
|
const { data: sshKeys } = api.sshKey.all.useQuery();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const { mutateAsync, isLoading } = api.compose.update.useMutation();
|
const { mutateAsync, isLoading } = api.compose.update.useMutation();
|
||||||
|
|
||||||
const { mutateAsync: generateSSHKey, isLoading: isGeneratingSSHKey } =
|
|
||||||
api.compose.generateSSHKey.useMutation();
|
|
||||||
|
|
||||||
const { mutateAsync: removeSSHKey, isLoading: isRemovingSSHKey } =
|
|
||||||
api.compose.removeSSHKey.useMutation();
|
|
||||||
const form = useForm<GitProvider>({
|
const form = useForm<GitProvider>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
branch: "",
|
branch: "",
|
||||||
repositoryURL: "",
|
repositoryURL: "",
|
||||||
composePath: "./docker-compose.yml",
|
composePath: "./docker-compose.yml",
|
||||||
|
sshKey: undefined,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GitProviderSchema),
|
resolver: zodResolver(GitProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -63,6 +61,7 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
|
sshKey: data.customGitSSHKeyId || undefined,
|
||||||
branch: data.customGitBranch || "",
|
branch: data.customGitBranch || "",
|
||||||
repositoryURL: data.customGitUrl || "",
|
repositoryURL: data.customGitUrl || "",
|
||||||
composePath: data.composePath,
|
composePath: data.composePath,
|
||||||
@@ -74,6 +73,7 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
|
|||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
customGitBranch: values.branch,
|
customGitBranch: values.branch,
|
||||||
customGitUrl: values.repositoryURL,
|
customGitUrl: values.repositoryURL,
|
||||||
|
customGitSSHKeyId: values.sshKey === "none" ? null : values.sshKey,
|
||||||
composeId,
|
composeId,
|
||||||
sourceType: "git",
|
sourceType: "git",
|
||||||
composePath: values.composePath,
|
composePath: values.composePath,
|
||||||
@@ -94,123 +94,72 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
|
|||||||
className="flex flex-col gap-4"
|
className="flex flex-col gap-4"
|
||||||
>
|
>
|
||||||
<div className="grid md:grid-cols-2 gap-4 ">
|
<div className="grid md:grid-cols-2 gap-4 ">
|
||||||
<div className="md:col-span-2 space-y-4">
|
<div className="flex items-end col-span-2 gap-4">
|
||||||
<FormField
|
<div className="grow">
|
||||||
control={form.control}
|
<FormField
|
||||||
name="repositoryURL"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="repositoryURL"
|
||||||
<FormItem>
|
render={({ field }) => (
|
||||||
<FormLabel className="flex flex-row justify-between">
|
<FormItem>
|
||||||
Repository URL
|
<FormLabel className="flex flex-row justify-between">
|
||||||
<div className="flex gap-2">
|
Repository URL
|
||||||
<Dialog>
|
</FormLabel>
|
||||||
<DialogTrigger className="flex flex-row gap-2">
|
<FormControl>
|
||||||
<LockIcon className="size-4 text-muted-foreground" />?
|
<Input placeholder="git@bitbucket.org" {...field} />
|
||||||
</DialogTrigger>
|
</FormControl>
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
<FormMessage />
|
||||||
<DialogHeader>
|
</FormItem>
|
||||||
<DialogTitle>Private Repository</DialogTitle>
|
)}
|
||||||
<DialogDescription>
|
/>
|
||||||
If your repository is private is necessary to
|
</div>
|
||||||
generate SSH Keys to add to your git provider.
|
{sshKeys && sshKeys.length > 0 ? (
|
||||||
</DialogDescription>
|
<FormField
|
||||||
</DialogHeader>
|
control={form.control}
|
||||||
<div className="grid gap-4 py-4">
|
name="sshKey"
|
||||||
<div className="relative">
|
render={({ field }) => (
|
||||||
<Textarea
|
<FormItem className="basis-40">
|
||||||
placeholder="Please click on Generate SSH Key"
|
<FormLabel className="w-full inline-flex justify-between">
|
||||||
className="no-scrollbar h-64 text-muted-foreground"
|
SSH Key
|
||||||
disabled={!data?.customGitSSHKey}
|
<LockIcon className="size-4 text-muted-foreground" />
|
||||||
contentEditable={false}
|
</FormLabel>
|
||||||
value={
|
<FormControl>
|
||||||
data?.customGitSSHKey ||
|
<Select
|
||||||
"Please click on Generate SSH Key"
|
key={field.value}
|
||||||
}
|
onValueChange={field.onChange}
|
||||||
/>
|
defaultValue={field.value}
|
||||||
<button
|
value={field.value}
|
||||||
type="button"
|
>
|
||||||
className="absolute right-2 top-2"
|
<SelectTrigger>
|
||||||
onClick={() => {
|
<SelectValue placeholder="Select a key" />
|
||||||
copy(
|
</SelectTrigger>
|
||||||
data?.customGitSSHKey ||
|
<SelectContent>
|
||||||
"Generate a SSH Key",
|
<SelectGroup>
|
||||||
);
|
{sshKeys?.map((sshKey) => (
|
||||||
toast.success("SSH Copied to clipboard");
|
<SelectItem
|
||||||
}}
|
key={sshKey.sshKeyId}
|
||||||
|
value={sshKey.sshKeyId}
|
||||||
>
|
>
|
||||||
<CopyIcon className="size-4" />
|
{sshKey.name}
|
||||||
</button>
|
</SelectItem>
|
||||||
</div>
|
))}
|
||||||
</div>
|
<SelectItem value="none">None</SelectItem>
|
||||||
<DialogFooter className="flex sm:justify-between gap-3.5 flex-col sm:flex-col w-full">
|
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
|
||||||
<div className="flex flex-row gap-2 w-full justify-between flex-wrap">
|
</SelectGroup>
|
||||||
{data?.customGitSSHKey && (
|
</SelectContent>
|
||||||
<Button
|
</Select>
|
||||||
variant="destructive"
|
</FormControl>
|
||||||
isLoading={
|
</FormItem>
|
||||||
isGeneratingSSHKey || isRemovingSSHKey
|
)}
|
||||||
}
|
/>
|
||||||
className="max-sm:w-full"
|
) : (
|
||||||
onClick={async () => {
|
<Button
|
||||||
await removeSSHKey({
|
variant="secondary"
|
||||||
composeId,
|
onClick={() => router.push("/dashboard/settings/ssh-keys")}
|
||||||
})
|
type="button"
|
||||||
.then(async () => {
|
>
|
||||||
toast.success("SSH Key Removed");
|
<KeyRoundIcon className="size-4" /> Add SSH Key
|
||||||
await refetch();
|
</Button>
|
||||||
})
|
)}
|
||||||
.catch(() => {
|
|
||||||
toast.error(
|
|
||||||
"Error to remove the SSH Key",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Remove SSH Key
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
isLoading={
|
|
||||||
isGeneratingSSHKey || isRemovingSSHKey
|
|
||||||
}
|
|
||||||
className="max-sm:w-full"
|
|
||||||
onClick={async () => {
|
|
||||||
await generateSSHKey({
|
|
||||||
composeId,
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
toast.success("SSH Key Generated");
|
|
||||||
await refetch();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error(
|
|
||||||
"Error to generate the SSH Key",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Generate SSH Key
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
Is recommended to remove the SSH Key if you want
|
|
||||||
to deploy a public repository.
|
|
||||||
</span>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="git@bitbucket.org" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<FormField
|
<FormField
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { api } from "@/utils/api";
|
|||||||
import { GitBranch, LockIcon } from "lucide-react";
|
import { GitBranch, LockIcon } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
|
|
||||||
import { ComposeFileEditor } from "../compose-file-editor";
|
import { ComposeFileEditor } from "../compose-file-editor";
|
||||||
import { SaveGitProviderCompose } from "./save-git-provider-compose";
|
import { SaveGitProviderCompose } from "./save-git-provider-compose";
|
||||||
|
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
|
||||||
|
|
||||||
type TabState = "github" | "git" | "raw";
|
type TabState = "github" | "git" | "raw";
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -7,12 +8,11 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { Dices } from "lucide-react";
|
import { Dices } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -5,11 +6,10 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import React from "react";
|
|
||||||
import { ShowProviderFormCompose } from "./generic/show";
|
|
||||||
import { ComposeActions } from "./actions";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import React from "react";
|
||||||
|
import { ComposeActions } from "./actions";
|
||||||
|
import { ShowProviderFormCompose } from "./generic/show";
|
||||||
interface Props {
|
interface Props {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import dynamic from "next/dynamic";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -6,6 +5,7 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -16,8 +16,8 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
export const DockerLogs = dynamic(
|
export const DockerLogs = dynamic(
|
||||||
() =>
|
() =>
|
||||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||||
@@ -30,12 +30,14 @@ export const DockerLogs = dynamic(
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
appName: string;
|
appName: string;
|
||||||
|
appType: "stack" | "docker-compose";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShowDockerLogsCompose = ({ appName }: Props) => {
|
export const ShowDockerLogsCompose = ({ appName, appType }: Props) => {
|
||||||
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
|
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
|
||||||
{
|
{
|
||||||
appName,
|
appName,
|
||||||
|
appType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!appName,
|
enabled: !!appName,
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -16,7 +15,8 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Label } from "@/components/ui/label";
|
import { api } from "@/utils/api";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { DockerMonitoring } from "../../monitoring/docker/show";
|
import { DockerMonitoring } from "../../monitoring/docker/show";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -31,6 +31,7 @@ export const ShowMonitoringCompose = ({
|
|||||||
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
|
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
|
||||||
{
|
{
|
||||||
appName: appName,
|
appName: appName,
|
||||||
|
appType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!appName,
|
enabled: !!appName,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -7,7 +9,6 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -16,16 +17,15 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { SquarePen } from "lucide-react";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { SquarePen } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const updateComposeSchema = z.object({
|
const updateComposeSchema = z.object({
|
||||||
name: z.string().min(1, {
|
name: z.string().min(1, {
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
} from "@/components/ui/command";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -11,36 +18,29 @@ import {
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormDescription,
|
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PlusIcon } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
||||||
import {
|
|
||||||
Command,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandInput,
|
|
||||||
CommandItem,
|
|
||||||
} from "@/components/ui/command";
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { z } from "zod";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { PlusIcon } from "lucide-react";
|
||||||
|
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const AddPostgresBackup1Schema = z.object({
|
const AddPostgresBackup1Schema = z.object({
|
||||||
destinationId: z.string().min(1, "Destination required"),
|
destinationId: z.string().min(1, "Destination required"),
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
} from "@/components/ui/command";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -18,28 +25,21 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Pencil, CheckIcon, ChevronsUpDown, PenBoxIcon } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
|
||||||
Command,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandInput,
|
|
||||||
CommandItem,
|
|
||||||
} from "@/components/ui/command";
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { CheckIcon, ChevronsUpDown, PenBoxIcon, Pencil } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const UpdateBackupSchema = z.object({
|
const UpdateBackupSchema = z.object({
|
||||||
destinationId: z.string().min(1, "Destination required"),
|
destinationId: z.string().min(1, "Destination required"),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
import { FitAddon } from "xterm-addon-fit";
|
import { FitAddon } from "xterm-addon-fit";
|
||||||
import "@xterm/xterm/css/xterm.css";
|
import "@xterm/xterm/css/xterm.css";
|
||||||
|
|
||||||
@@ -23,8 +23,11 @@ export const DockerLogsId: React.FC<Props> = ({ id, containerId }) => {
|
|||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
cols: 80,
|
cols: 80,
|
||||||
rows: 30,
|
rows: 30,
|
||||||
lineHeight: 1.4,
|
lineHeight: 1.25,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily:
|
||||||
|
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||||
|
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import dynamic from "next/dynamic";
|
|
||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -9,6 +7,8 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import type React from "react";
|
||||||
export const DockerLogsId = dynamic(
|
export const DockerLogsId = dynamic(
|
||||||
() =>
|
() =>
|
||||||
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
import("@/components/dashboard/docker/logs/docker-logs-id").then(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react";
|
|
||||||
import type { ColumnDef } from "@tanstack/react-table";
|
import type { ColumnDef } from "@tanstack/react-table";
|
||||||
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as React from "react";
|
|
||||||
import {
|
import {
|
||||||
type ColumnFiltersState,
|
type ColumnFiltersState,
|
||||||
type SortingState,
|
type SortingState,
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
useReactTable,
|
useReactTable,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { api, type RouterOutputs } from "@/utils/api";
|
import { type RouterOutputs, api } from "@/utils/api";
|
||||||
import { columns } from "./colums";
|
import { columns } from "./colums";
|
||||||
export type Container = NonNullable<
|
export type Container = NonNullable<
|
||||||
RouterOutputs["docker"]["getContainers"]
|
RouterOutputs["docker"]["getContainers"]
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
const Terminal = dynamic(
|
const Terminal = dynamic(
|
||||||
() => import("./docker-terminal").then((e) => e.DockerTerminal),
|
() => import("./docker-terminal").then((e) => e.DockerTerminal),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
import { FitAddon } from "xterm-addon-fit";
|
import { FitAddon } from "xterm-addon-fit";
|
||||||
import "@xterm/xterm/css/xterm.css";
|
import "@xterm/xterm/css/xterm.css";
|
||||||
import { AttachAddon } from "@xterm/addon-attach";
|
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { AttachAddon } from "@xterm/addon-attach";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -10,14 +12,12 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { validateAndFormatYAML } from "../application/advanced/traefik/update-traefik-config";
|
import { validateAndFormatYAML } from "../application/advanced/traefik/update-traefik-config";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
|
||||||
|
|
||||||
const UpdateServerMiddlewareConfigSchema = z.object({
|
const UpdateServerMiddlewareConfigSchema = z.object({
|
||||||
traefikConfig: z.string(),
|
traefikConfig: z.string(),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { Workflow, Folder, FileIcon } from "lucide-react";
|
|
||||||
import { Tree } from "@/components/ui/file-tree";
|
import { Tree } from "@/components/ui/file-tree";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { FileIcon, Folder, Workflow } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ShowTraefikFile } from "./show-traefik-file";
|
import { ShowTraefikFile } from "./show-traefik-file";
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import React, { useEffect } from "react";
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { api } from "@/utils/api";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { z } from "zod";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { ShowMariadbResources } from "./show-mariadb-resources";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -15,8 +8,15 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { ShowVolumes } from "../volumes/show-volumes";
|
import { ShowVolumes } from "../volumes/show-volumes";
|
||||||
|
import { ShowMariadbResources } from "./show-mariadb-resources";
|
||||||
|
|
||||||
const addDockerImage = z.object({
|
const addDockerImage = z.object({
|
||||||
dockerImage: z.string().min(1, "Docker image is required"),
|
dockerImage: z.string().min(1, "Docker image is required"),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -21,7 +22,6 @@ import React, { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
|
|
||||||
const addResourcesMariadb = z.object({
|
const addResourcesMariadb = z.object({
|
||||||
memoryReservation: z.number().nullable().optional(),
|
memoryReservation: z.number().nullable().optional(),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user