Compare commits

...

254 Commits

Author SHA1 Message Date
Mauricio Siu
9bf88b90c3 Merge pull request #280 from Dokploy/canary
v0.5.0
2024-07-27 15:20:43 -06:00
Mauricio Siu
947d2217df chore(version): bump version 2024-07-27 15:05:31 -06:00
Mauricio Siu
9eba4f8b84 Merge pull request #279 from Dokploy/fix/remove-expose-port-traefik
feat(traefik): add toggle for disable the traefik dashboard
2024-07-27 13:15:00 -06:00
Mauricio Siu
f0d0e4c1e2 feat(traefik): add toggle for disable the traefik dashboard 2024-07-27 13:09:20 -06:00
Mauricio Siu
24cb47bcb1 Merge pull request #276 from lorenzomigliorero/feat/shared-ssh
feat: shared ssh
2024-07-27 12:39:33 -06:00
Lorenzo Migliorero
9cf4a5e7a3 Merge branch 'feat/shared-ssh' of github.com:lorenzomigliorero/dokploy into feat/shared-ssh 2024-07-27 19:57:16 +02:00
Lorenzo Migliorero
7b91d67655 feat: remove -R from canary and prod shell scripts 2024-07-27 19:57:10 +02:00
Mauricio Siu
ca0acf0445 refactor: set SSH path to permission 700 2024-07-27 10:11:46 -06:00
Lorenzo Migliorero
576ff02773 refactor: change ssh private key permission to 600 2024-07-27 11:55:44 +02:00
Lorenzo Migliorero
d8b28107cd refactor: prefer write file 2024-07-27 11:51:16 +02:00
Mauricio Siu
9898cac2f5 Merge pull request #277 from Dokploy/refactor/spawn-async
refactor: add error message in error builder
2024-07-27 00:49:44 -06:00
Mauricio Siu
dd9bed4c2b refactor: add error message in error builder 2024-07-27 00:08:40 -06:00
Lorenzo Migliorero
14c8ae675a fix: linting 2024-07-26 10:14:13 +02:00
Lorenzo Migliorero
bda0689e18 fix: ssh permission 2024-07-26 10:11:38 +02:00
Lorenzo Migliorero
7e39be4ca1 fix: migrations 2024-07-26 09:51:35 +02:00
Mauricio Siu
2e3a7c6164 Merge branch 'canary' into feat/shared-ssh 2024-07-26 01:14:55 -06:00
Mauricio Siu
f02f75e3a0 Merge pull request #267 from lorenzomigliorero/feat/condition-certificate
feat: condition domain certificate field
2024-07-26 01:13:43 -06:00
Mauricio Siu
fe51dd6b0a refactor: remove flush sync 2024-07-26 01:04:52 -06:00
Lorenzo Migliorero
2724336cad feat: ssh key type and whitespaces 2024-07-25 23:32:52 +02:00
Lorenzo Migliorero
12bd017d07 fix: type 2024-07-25 22:53:48 +02:00
Lorenzo Migliorero
a2eff67d44 feat: generate ssh key 2024-07-25 22:52:39 +02:00
Lorenzo Migliorero
71555a15f8 feat: last used at 2024-07-25 22:15:40 +02:00
Lorenzo Migliorero
c681aa2e9f feat: compose app 2024-07-25 22:10:35 +02:00
Lorenzo Migliorero
1f81ebd4fe feat: ssh keys filesystel 2024-07-25 20:16:49 +02:00
Lorenzo Migliorero
d243470029 feat: link field with application 2024-07-25 19:32:02 +02:00
Mauricio Siu
054e581023 Merge pull request #271 from Dokploy/262-ssh-keys-are-not-reusable-after-a-dokploy-update
refactor(directories): add 600 permissions to SSH_PATH #262
2024-07-25 09:52:08 -06:00
Lorenzo Migliorero
f866250c25 feat: new table and crud operations 2024-07-25 15:19:03 +02:00
Lorenzo Migliorero
ee58672d58 refactor: dry validation rules 2024-07-25 11:03:14 +02:00
Mauricio Siu
135894da2a Merge branch 'canary' into 262-ssh-keys-are-not-reusable-after-a-dokploy-update 2024-07-25 02:10:51 -06:00
Mauricio Siu
06c8688ddd Merge pull request #269 from binaryYuki/canary
fix: Ensure Proper Permissions for acme.json in Traefik Configuration
2024-07-25 02:10:08 -06:00
Mauricio Siu
d8474b8aa3 Merge pull request #273 from Dokploy/258-when-an-ipv6-address-is-available-the-user-may-be-directed-to-sign-into-a-url-which-will-not-work-for-them
refactor(script): make ipv4 first and format the url when is ipv6 #258
2024-07-25 02:06:42 -06:00
Mauricio Siu
9a4b474cdc refactor(traefik-setup): change order of chmodsync 2024-07-25 02:04:54 -06:00
Mauricio Siu
1519a71535 refactor(script): make ipv4 first and format the url when is ipv6 #258 2024-07-25 01:49:41 -06:00
Mauricio Siu
9e47103131 refactor(script): make ipv4 first and format the url when is ipv6 #258 2024-07-25 01:48:52 -06:00
Yuki
ef689f06d6 Update traefik-setup.ts 2024-07-25 15:25:04 +08:00
Yuki
54c7572447 check if exist
Check if the file already exist, if yes modify its mode
2024-07-25 15:18:59 +08:00
Lorenzo Migliorero
4cacc6b3d1 fix: type 2024-07-25 09:15:13 +02:00
Mauricio Siu
8b193d4317 Merge pull request #272 from Dokploy/fix/templates-volumes
fix(templates): change path of volumes to be in files folder to prevent the volumes delete in each deploy
2024-07-25 01:11:23 -06:00
Mauricio Siu
e72add74c3 fix(templates): change path of volumes to be in files folder to prevent delete the volumes 2024-07-25 01:02:10 -06:00
Lorenzo Migliorero
115c8641e7 fix: nullable type 2024-07-25 08:59:10 +02:00
Mauricio Siu
0af532f87e refactor(directories): add 600 permissions to SSH_PATH #262 2024-07-25 00:51:15 -06:00
Mauricio Siu
d1bd2b29fe Merge pull request #270 from Dokploy/fix/undefined-host-databases
fix(databases): add ip to useeffect deps
2024-07-25 00:00:00 -06:00
Mauricio Siu
734a6607c8 fix(databases): add ip to useeffect deps 2024-07-24 23:31:03 -06:00
Mauricio Siu
d3a2b03bb7 Merge pull request #268 from lorenzomigliorero/fix/terminal-select-text
fix: terminal monospace font
2024-07-24 23:16:16 -06:00
Mauricio Siu
9a1436d0ae Merge pull request #265 from Dokploy/feat/replace-docker-builder
refactor(docker-build): replace docker build with the command
2024-07-24 21:42:59 -06:00
Yuki
087e2c81cc accept suggestion by coderabbit
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-07-25 11:34:18 +08:00
Yuki
32f35a6ca0 Update traefik-setup.ts 2024-07-25 11:25:28 +08:00
Lorenzo Migliorero
7fd35999b1 fix: terminal text selectable 2024-07-24 19:38:25 +02:00
Lorenzo Migliorero
fa5b75e6fb fix: type 2024-07-24 19:01:18 +02:00
Lorenzo Migliorero
7262e0debe Merge branch 'canary' of github.com:Dokploy/dokploy into feat/condition-certificate 2024-07-24 18:57:55 +02:00
Lorenzo Migliorero
13c686c228 feat: condition certificate 2024-07-24 18:47:52 +02:00
Mauricio Siu
083bb7b87d Merge pull request #264 from lorenzomigliorero/fix/http-to-https
fix: http to https
2024-07-24 02:45:27 -06:00
Lorenzo Migliorero
1b7ecd5a41 refactor: remove comment 2024-07-24 10:39:15 +02:00
Mauricio Siu
56b52b3f9c refactor(docker-build): replace docker build with the command 2024-07-24 00:04:59 -06:00
Lorenzo Migliorero
88f67b1c71 test: reset baseapp 2024-07-23 21:06:57 +02:00
Lorenzo Migliorero
75bd10cbd5 fix: http to https 2024-07-23 20:55:35 +02:00
Mauricio Siu
d3397cfbd0 Merge pull request #263 from lorenzomigliorero/fix/update-redirect
fix: update redirect
2024-07-23 10:11:23 -06:00
Lorenzo Migliorero
a4bf48fa68 fix: update redirect 2024-07-23 14:34:46 +02:00
Mauricio Siu
dfe3294088 Merge pull request #249 from lorenzomigliorero/feat/buildargs
feat: buildargs
2024-07-22 21:19:14 -06:00
Mauricio Siu
5b1ca4eafc refactor: rename form 2024-07-22 21:13:57 -06:00
Mauricio Siu
82721251cc refactor: remove secrets 2024-07-22 21:12:26 -06:00
Mauricio Siu
cd051b72fc refactor: remove imports 2024-07-22 21:12:13 -06:00
Mauricio Siu
411070cfcc Merge pull request #260 from lorenzomigliorero/fix/docker-compose-monitoring
fix: docker compose monitoring
2024-07-22 20:24:58 -06:00
Lorenzo Migliorero
8230c1ba91 fix: docker compose monitoring 2024-07-22 22:43:47 +02:00
Lorenzo Migliorero
8b7df6ce16 refactor: single form 2024-07-22 22:17:24 +02:00
Lorenzo Migliorero
6d71eac221 revert: env file 2024-07-22 21:42:07 +02:00
Mauricio Siu
fd3e4a8bc7 Merge pull request #243 from barraudSamuel/feat/jellyfin
feat: add jellyfin template
2024-07-22 13:03:14 -06:00
Mauricio Siu
c13eb65b5a refactor: format 2024-07-22 12:58:14 -06:00
Samuel
bb13a09def Merge branch 'feat/jellyfin' of github.com:barraudSamuel/dokploy into feat/jellyfin 2024-07-22 20:35:12 +03:00
Samuel
94bcea36c6 feat: add jellyfin template 2024-07-22 20:34:26 +03:00
Mauricio Siu
028b9b3f7b Merge pull request #257 from lorenzomigliorero/fix/docker-compose-logs
fix: docker compose logs
2024-07-22 10:32:42 -06:00
Lorenzo Migliorero
b85639f98e fix: add apptype param 2024-07-22 17:42:31 +02:00
Lorenzo Migliorero
7c981b2aac fix: compose containers 2024-07-22 17:33:42 +02:00
Lorenzo Migliorero
59b072e7e0 feat: scope to dockerfile buildtype 2024-07-22 12:24:12 +02:00
Lorenzo Migliorero
6780fa9688 feat: add doc link 2024-07-22 12:21:51 +02:00
Lorenzo Migliorero
ff47a157c7 fix: split by new line char 2024-07-22 12:14:07 +02:00
Lorenzo Migliorero
3601abc4c1 feat: apply buildargs 2024-07-22 11:52:20 +02:00
Mauricio Siu
b1a48d4636 refactor: update job 2024-07-22 03:51:07 -06:00
Mauricio Siu
a15eb3b229 refactor: update jobs 2024-07-22 03:45:45 -06:00
Mauricio Siu
c34c4b244e Merge pull request #251 from Dokploy/canary
v0.4.0
2024-07-22 03:38:47 -06:00
Mauricio Siu
4991e4b1f2 chore(bump): upgrade version 2024-07-22 03:20:43 -06:00
Lorenzo Migliorero
b3b7439617 feat: add build-time variables form 2024-07-22 11:17:59 +02:00
Mauricio Siu
b7c061dcb4 Fix(Builds): Make docker builds 98% faster (#250)
Feat: add circle ci as a runner
2024-07-22 03:14:25 -06:00
Lorenzo Migliorero
e00cbaeb8a build: ignore vscode 2024-07-22 09:59:53 +02:00
Lorenzo Migliorero
655e29d96b feat: buildargs column 2024-07-22 09:59:12 +02:00
Lorenzo Migliorero
7240ff38f1 build: pkg push command 2024-07-22 09:58:49 +02:00
barraud-samuel
4a08bacba0 Merge branch 'Dokploy:canary' into feat/jellyfin 2024-07-22 08:37:17 +02:00
Samuel
54aaa511d5 feat: add jellyfin template 2024-07-22 09:20:16 +03:00
Mauricio Siu
701319efdd Merge pull request #248 from Dokploy/245-volumes-are-canceled-on-deployment
Fix(volumes):Make file mounts are persistent
2024-07-21 20:26:53 -06:00
Mauricio Siu
0fdf176648 test(drop): add code directory 2024-07-21 19:09:38 -06:00
Mauricio Siu
f62d835f57 Merge pull request #247 from lorenzomigliorero/build/pnpm-node-setup
build: pnpm, node and lock file versioning
2024-07-21 19:06:05 -06:00
Mauricio Siu
5bb4710952 Merge branch 'canary' into 245-volumes-are-canceled-on-deployment 2024-07-21 19:05:27 -06:00
Mauricio Siu
9b71ce9388 Merge branch 'canary' into 245-volumes-are-canceled-on-deployment 2024-07-21 18:59:54 -06:00
Mauricio Siu
175d1ec432 refactor: delete comments 2024-07-21 18:57:16 -06:00
Mauricio Siu
8454e4f781 refactor(volumes): rework files volumes to be more simple and persistent 2024-07-21 18:55:57 -06:00
Mauricio Siu
2e79c7230f refactor(volumes): rework volumes and paths 2024-07-21 18:02:42 -06:00
Lorenzo Migliorero
7ddd3bb8a0 build: update pkg engine 2024-07-22 01:51:01 +02:00
Lorenzo Migliorero
b889a9d248 Merge branch 'canary' of github.com:Dokploy/dokploy into build/pnpm-node-setup 2024-07-22 01:45:29 +02:00
Lorenzo Migliorero
fc21c96cd1 build: fix lock conflict 2024-07-22 01:44:03 +02:00
Lorenzo Migliorero
0341b19c9f build: pnpm and node versioning 2024-07-22 01:29:08 +02:00
Mauricio Siu
43095f2435 Merge pull request #246 from Dokploy/216-domains-for-services-created-via-template-1
Refactor: enable autodeploy by default
2024-07-21 12:43:09 -06:00
Samuel
ad696ea54a feat: add jellyfin template 2024-07-21 11:05:44 +03:00
Mauricio Siu
f6128bdf0c refactor(webhook): change autodeploy message 2024-07-21 01:36:41 -06:00
Mauricio Siu
6be6ec940a refactor(applications): enable autodeploy by default 2024-07-21 01:36:18 -06:00
Mauricio Siu
ba9fc59805 refactor(databases): show hide and show icon in enviroment tab 2024-07-21 01:35:52 -06:00
Mauricio Siu
63a1039439 Merge pull request #242 from Dokploy/131-support-for-folder-deployment-and-drag-n-drop
feat(drag-n-drop): add support for drag n drop projects via zip #131
2024-07-21 00:51:41 -06:00
Mauricio Siu
d52692c6a3 feat(drag-n-drop): add support for drag n drop projects via zip #131 2024-07-21 00:44:08 -06:00
Mauricio Siu
b4511ca7a2 Merge pull request #240 from Dokploy/227-add-support-for-environment-variables-during-dockerfile-build-process-in-dokploy
refactor(dockerfile): create .env file when using dockerfile #227
2024-07-20 16:23:04 -06:00
Mauricio Siu
a3c24f1f2a Merge pull request #239 from Dokploy/fix/databases-internal-url
refactor(databases): show ip in external connection instead of hostname
2024-07-20 16:18:26 -06:00
Mauricio Siu
ca599f27f7 refactor(dockerfile): create .env file when using dockerfile #227 2024-07-20 16:17:10 -06:00
Mauricio Siu
f36de7b2f5 refactor(databases): show ip in external connection instead of hostname 2024-07-20 15:23:44 -06:00
Mauricio Siu
0ce055c001 Merge pull request #238 from Dokploy/202-feature-categorize-templates-for-improved-ux
refactor(templates): add tag input selector
2024-07-20 15:15:48 -06:00
Mauricio Siu
fc011a5661 refactor(templates): add tag input selector 2024-07-20 15:10:00 -06:00
Mauricio Siu
60d6d781be Merge pull request #237 from Dokploy/220-update-default-cnb-builder-image-to-herokubuilder24
feat(heroku-buildpacks): upgrade heroku to 24 version
2024-07-20 14:30:38 -06:00
Mauricio Siu
9270739eb6 feat(heroku-buildpacks): upgrade heroku to 24 version 2024-07-20 14:13:55 -06:00
Mauricio Siu
87b87b85c0 Merge pull request #236 from Dokploy/fix/traefik-warnings-restart
fix(traefik): add try catch when starting service
2024-07-20 13:58:11 -06:00
Mauricio Siu
496fd40fa3 refactor: update husky 2024-07-20 13:52:38 -06:00
Mauricio Siu
25fe080582 fix(traefik): add try catch when starting service 2024-07-20 13:37:12 -06:00
Mauricio Siu
1c41091372 Merge pull request #234 from Dokploy/27-feature-implement-email-resend-functionality-on-build-failure
Feat: add notifications provider
2024-07-20 13:03:55 -06:00
Mauricio Siu
9230178005 chore: update biome format 2024-07-20 02:55:17 -06:00
Mauricio Siu
fd092f1248 chore: comment temporaly comitlint 2024-07-20 02:52:04 -06:00
Mauricio Siu
736c186a66 fix(notifications): adjust types 2024-07-20 02:46:05 -06:00
Mauricio Siu
3d348ee762 refactor(notifications): remove secure port 2024-07-20 02:43:33 -06:00
Mauricio Siu
6e78f49c2f refactor: update logo url 2024-07-20 01:04:30 -06:00
Mauricio Siu
e77b30671b refactor(emails): add logo image 2024-07-20 00:56:44 -06:00
Mauricio Siu
244e1227c4 chore(husky): update script 2024-07-20 00:54:01 -06:00
Mauricio Siu
9934dac203 chore(config): update commit lint 2024-07-20 00:31:45 -06:00
Mauricio Siu
44ee326057 refactor(notifications): change render to renderAsync in emails 2024-07-19 23:56:48 -06:00
Mauricio Siu
18f892096b refactor: remove unused var 2024-07-19 22:15:08 -06:00
Mauricio Siu
9954d5b209 refactor(notification): split functions for each notification actio 2024-07-19 21:57:46 -06:00
Mauricio Siu
e0bde5cec9 refactor(notifications): minimize send notifications in more reusable fn 2024-07-19 01:41:01 -06:00
Mauricio Siu
2d4eaeb8b5 feat(notifications): add emails and methos for each action 2024-07-19 01:02:48 -06:00
Mauricio Siu
787506fb6b Merge branch 'canary' into 27-feature-implement-email-resend-functionality-on-build-failure 2024-07-18 20:39:24 -06:00
Mauricio Siu
50c8c3a43a Merge pull request #231 from kdurek/feat/add-commitlint
feat: add commitlint
2024-07-18 20:23:41 -06:00
Krzysztof Durek
1f09c06274 feat: add commitlint 2024-07-18 12:06:13 +02:00
Mauricio Siu
bb59a0cd3f Merge pull request #230 from Dokploy/canary
v0.3.3
2024-07-18 00:11:10 -06:00
Mauricio Siu
1e4217315b chore(version): bump version 2024-07-17 23:05:28 -06:00
Mauricio Siu
b373aca0ff Merge pull request #228 from anh-ld/feat/add-input-copy-button
feat: add copy function to visibility input
2024-07-17 22:28:21 -06:00
Anh (Daniel) Le
87e90cb30b fix: use copy-to-clipboard for visibility input 2024-07-18 11:15:35 +07:00
Mauricio Siu
135fabb852 Merge pull request #229 from kdurek/feat/update-dockerfile-and-cicd
ci: split workflows to separate files
2024-07-17 13:01:12 -06:00
Krzysztof Durek
b0b5b94bb7 Merge branch 'canary' of https://github.com/kdurek/dokploy into feat/update-dockerfile-and-cicd 2024-07-17 20:28:04 +02:00
Mauricio Siu
8f1c1e5202 Merge pull request #226 from kdurek/fix/husky-on-prod
fix: disable husky only on production
2024-07-17 11:52:04 -06:00
Anh (Daniel) Le
e4c243d7a6 fix: format toggle visibility input 2024-07-18 00:15:43 +07:00
Anh (Daniel) Le
c3bca21d14 fix: remove redundant selection api 2024-07-17 18:55:40 +07:00
Krzysztof Durek
1befdb76e7 ci: pnpm gets version from package.json packageManager field 2024-07-17 12:38:49 +02:00
Krzysztof Durek
35652c5c53 build: split build into multiple stages for caching 2024-07-17 12:33:21 +02:00
Krzysztof Durek
9524609092 ci: split workflows to separate files 2024-07-17 12:32:33 +02:00
Anh (Daniel) Le
38c16fe839 feat: add copy function to visibility input 2024-07-17 14:59:16 +07:00
Mauricio Siu
c0587b9409 Update FUNDING.yml 2024-07-16 19:45:41 -06:00
Krzysztof Durek
e249e878f6 fix: disable husky only on production 2024-07-16 14:11:44 +02:00
Krzysztof Durek
2d64815c12 fix: disable husky only on production 2024-07-16 12:05:27 +02:00
Mauricio Siu
af11bc8cd2 chore: add dependencies 2024-07-16 01:16:02 -06:00
Mauricio Siu
6779dec1ff Merge branch 'canary' into 27-feature-implement-email-resend-functionality-on-build-failure 2024-07-16 01:14:58 -06:00
Mauricio Siu
191a6112ce feat(notifications: add app build error providerd 2024-07-16 01:11:45 -06:00
Mauricio Siu
1bf518f768 chore: update migration 2024-07-16 00:39:26 -06:00
Mauricio Siu
d0d4182fc1 Merge pull request #224 from Dokploy/fix/remove-husky
chore(husky): remove script
2024-07-15 21:21:11 -06:00
Mauricio Siu
736f7c2d77 chore(husky): remove script 2024-07-15 21:10:46 -06:00
Mauricio Siu
6e38508477 Merge pull request #219 from kdurek/feat/refactor-format-and-lint
feat: update configs
2024-07-15 20:45:00 -06:00
Mauricio Siu
afe8160d46 Update biome.json 2024-07-15 20:26:39 -06:00
Mauricio Siu
ceb4ae62e2 Update biome.json 2024-07-15 20:25:15 -06:00
Krzysztof Durek
67d0dd5bf7 fix: update test to not throw typescript errors 2024-07-15 14:36:48 +02:00
Krzysztof Durek
6e28545c3f feat: update continuous integration 2024-07-15 13:59:21 +02:00
Krzysztof Durek
382208ae13 Merge branch 'canary' of https://github.com/Dokploy/dokploy into feat/refactor-format-and-lint 2024-07-15 01:13:09 +02:00
Krzysztof Durek
906e8de13b chore: format whole repository with new configs 2024-07-15 01:08:18 +02:00
Krzysztof Durek
7a5c71cda3 feat: update configs 2024-07-15 01:02:18 +02:00
Mauricio Siu
7bc6d0777d Merge pull request #217 from kdurek/feat/umami-template
feat: add umami template
2024-07-14 13:05:27 -06:00
Krzysztof Durek
f684ba7b1f fix: change umami version 2024-07-14 20:42:26 +02:00
Mauricio Siu
86ed884162 Merge pull request #215 from eremannisto/214-clarify-error-message-for-naming-validation
Clarify error message for naming validation in `AppName`
2024-07-14 12:32:23 -06:00
Krzysztof Durek
4ff178ea34 feat: add umami template 2024-07-14 20:28:37 +02:00
Ere Männistö
2eb5c331a1 Clarify error message for naming validation 2024-07-14 15:23:54 +03:00
Mauricio Siu
79ad0818f5 feat(notifications): add build failed and invitation emails from react-email 2024-07-14 02:49:21 -06:00
Mauricio Siu
84c10eec66 chore: add open collective organizations 2024-07-13 00:48:49 -06:00
Mauricio Siu
61673a40e3 Merge pull request #212 from Dokploy/210-every-time-i-create-a-server-on-hetzner-it-does-not-work-after-restart
fix(#210): Make dokploy services automatically start when a crash or restart server
2024-07-13 00:40:49 -06:00
Mauricio Siu
363ba1d20e fix(#210): remove restartpolicy in order to automatically restart the services in any case 2024-07-12 23:35:51 -06:00
Mauricio Siu
5fadd73732 feat: implement test connection for all the providers 2024-07-12 01:29:24 -06:00
Mauricio Siu
44e6a117dd Merge pull request #208 from Dokploy/canary
v0.3.2
2024-07-11 23:21:32 -06:00
Mauricio Siu
86bb119052 chore(version): bump version 2024-07-11 21:24:36 -06:00
Mauricio Siu
54c70ff177 Merge pull request #206 from Dokploy/feat/add-constraints-dokploy-services
refactor(setup): add constraints to dokploy services
2024-07-10 22:47:22 -06:00
Mauricio Siu
eabe14e4c3 refactor(setup): add constraints to dokploy services 2024-07-10 22:40:13 -06:00
Mauricio Siu
342ff4b589 feat(notifications): add delete notification modal 2024-07-09 01:16:06 -06:00
Mauricio Siu
680811357b feat(notifications): WIP add schema and modal 2024-07-09 01:14:09 -06:00
Mauricio Siu
9d0edd810e Merge pull request #204 from henriklovhaug/canary
[Docs/style]: Fix minor spelling error
2024-07-08 10:30:09 -06:00
Henrik Tøn Løvhaug
35ff65a871 Fix spelling 2024-07-08 10:16:26 +02:00
Mauricio Siu
675fbb7692 Merge pull request #200 from ciocan/feat/listmonk
feat: listmonk template
2024-07-06 17:38:29 -06:00
Mauricio Siu
295bd06918 Update templates.ts 2024-07-06 17:17:14 -06:00
Mauricio Siu
89635fa27b Merge branch 'canary' into feat/listmonk 2024-07-06 17:16:01 -06:00
Mauricio Siu
60b19616c1 Update docker-compose.yml 2024-07-06 17:15:17 -06:00
Mauricio Siu
3884dc9259 Merge pull request #198 from ciocan/feat/doublezero
feat: doublezero template
2024-07-06 17:13:27 -06:00
Mauricio Siu
e8648732be Update index.ts 2024-07-06 17:07:19 -06:00
Mauricio Siu
0f0f32a40d Update index.ts 2024-07-06 17:06:40 -06:00
Radu Ciocan
1b91376f5e feat: listmonk template 2024-07-06 23:01:01 +01:00
Radu Ciocan
aa347abfcd fix: code review changes 2024-07-06 20:30:23 +01:00
Radu Ciocan
e49b190c32 Update templates/doublezero/docker-compose.yml
Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
2024-07-06 20:13:12 +01:00
Mauricio Siu
bfdc73f8d1 Merge pull request #197 from Dokploy/canary
v0.3.1
2024-07-06 12:01:07 -06:00
Mauricio Siu
525a711c55 refactor(settings): throw error and log the error 2024-07-06 11:55:21 -06:00
Mauricio Siu
e0a0f97f70 refactor(settings): add console logs for errors 2024-07-06 11:34:33 -06:00
Radu Ciocan
66017c8623 fix: update template description 2024-07-06 14:34:30 +01:00
Radu Ciocan
bab93f8145 feat: doublezero template 2024-07-06 14:29:32 +01:00
Mauricio Siu
45aae520d8 refacto(github): comment github app 2024-07-05 22:48:32 -06:00
Mauricio Siu
c8f14c322e chore(version): bump version 2024-07-05 13:43:56 -06:00
Mauricio Siu
334466f28f Merge pull request #196 from Dokploy/fix/remove-registry-login-on-self-hosted
fix(registry): remove login on self hosted
2024-07-05 12:32:04 -06:00
Mauricio Siu
22328eeb83 fix(registry): remove login on self hosted 2024-07-05 12:24:13 -06:00
Mauricio Siu
c2201a304e Merge pull request #192 from kucherenko/canary
feat(open-webui): add open-webui template
2024-07-04 10:51:16 -06:00
Mauricio Siu
f77de1bc52 Update templates.ts 2024-07-04 10:43:30 -06:00
Andrey Kucherenko
74d6b2d591 Merge branch 'Dokploy:canary' into canary 2024-07-04 14:00:59 +03:00
Andrey Kucherenko
b7cc3283e2 chore(review): update code based on code review 2024-07-04 12:27:45 +03:00
Mauricio Siu
5dfa6c5194 Merge pull request #193 from Dokploy/fix/custom-internal-url-with-port
fix(git): set 22 port in git add host
2024-07-04 00:58:57 -06:00
Mauricio Siu
59d662396e fix(git): set 22 port in git add host 2024-07-04 00:39:11 -06:00
Mauricio Siu
cbfcae4ab6 Merge pull request #191 from DarkPhoenix2704/update-nocodb
chore: bump nocodb template version
2024-07-03 13:19:07 -06:00
Andrey Kucherenko
152a54b251 feat(open-webui): add open-webui template 2024-07-03 18:21:01 +03:00
DarkPhoenix2704
1113669bfd chore: bump version 2024-07-03 17:09:25 +05:30
Mauricio Siu
4d251271b9 refactor(webhook): use findAdmin() fn 2024-07-02 21:46:06 -06:00
Mauricio Siu
21263a217c Merge pull request #188 from Dokploy/feat/enable-autodeploy-github
feat(github-webhooks): #186 implement github autodeployment with zero…
2024-07-02 21:12:35 -06:00
Mauricio Siu
3322fdf937 Merge pull request #189 from slowlydev/feat/enable-autodeploy-github
Add web hook verification to GitHub Autodeploy
2024-07-02 18:02:06 -06:00
Slowlydev
b415e2fba1 feat(deploy-github): add webhook secret verification before deploying 2024-07-02 17:35:33 +02:00
Slowlydev
8888a4ce5c chore(deps): add @octokit/webhooks for verifying 2024-07-02 17:35:01 +02:00
Slowlydev
dd3ba7e836 feat(github-redirect): save webhook secret into database on redirect 2024-07-02 17:33:02 +02:00
Slowlydev
cb6d8fceb4 chore: add drizzle migrations for github webhook secret 2024-07-02 17:32:21 +02:00
Slowlydev
d8641b7b4e feat: add github webhook secret to admin schema 2024-07-02 17:31:38 +02:00
Mauricio Siu
997a8395b1 feat: add button to view github app 2024-07-02 00:11:40 -06:00
Mauricio Siu
d2420ed6e8 feat(github-webhooks): #186 implement github autodeployment with zero configuration 2024-07-01 23:43:08 -06:00
Mauricio Siu
64ada7020a Merge pull request #185 from Dokploy/canary
v0.3.0
2024-07-01 00:01:16 -06:00
Mauricio Siu
faf24dfa25 chore(version): bump version 2024-06-30 23:53:53 -06:00
Mauricio Siu
6f4bf428c7 Merge pull request #184 from Dokploy/feat/glitchtip
feat: add glitchtip template
2024-06-30 23:53:51 -06:00
Mauricio Siu
a43627d869 feat: add glitchtip template 2024-06-30 23:49:29 -06:00
Mauricio Siu
addd102d39 Merge pull request #180 from Dokploy/feat/templates
Feat/templates
2024-06-30 22:36:15 -06:00
Mauricio Siu
0d85fd0e3c feat: add metabase template 2024-06-30 22:31:23 -06:00
Mauricio Siu
86fc59d850 feat: add minio 2024-06-30 22:12:06 -06:00
Mauricio Siu
3cd3db6828 feat(tempaltes): add meilisearch, phpmyadmin and rocketchat 2024-06-30 21:39:24 -06:00
Mauricio Siu
2c6fd0f52b Merge branch 'canary' into feat/templates 2024-06-30 21:00:08 -06:00
Mauricio Siu
5fb682b58d Merge pull request #182 from DarkPhoenix2704/feat/nocodb-template
feat: nocodb template
2024-06-30 12:39:37 -06:00
DarkPhoenix2704
b77f330222 fix: use defined version 2024-07-01 00:01:16 +05:30
DarkPhoenix2704
f0b8f3eaa0 chore: update version 2024-07-01 00:01:16 +05:30
DarkPhoenix2704
36af22630b feat: nocodb template 2024-07-01 00:01:13 +05:30
Mauricio Siu
2d53a700f6 Merge pull request #175 from ephraimduncan/template/documenso
feat: add documenso as template
2024-06-30 01:20:18 -06:00
Mauricio Siu
00eeffee13 Merge pull request #176 from ephraimduncan/fix/calcom-template
fix: avoid using defined secrets in calcom template
2024-06-30 01:05:18 -06:00
Ephraim Atta-Duncan
7a32698031 fix: use version on image 2024-06-30 07:03:11 +00:00
Ephraim Atta-Duncan
9d834e1a79 fix: use hex instead of base64 for encryption variables 2024-06-30 07:00:29 +00:00
Ephraim Atta-Duncan
91819c2488 fix: use correct env var 2024-06-30 06:55:21 +00:00
Mauricio Siu
f64392469d refactor(compose): change error message 2024-06-30 00:42:00 -06:00
Mauricio Siu
889e72d21e refactor(templates): use port from env variable 2024-06-30 00:34:19 -06:00
Mauricio Siu
86165d1104 refactor(templates): remove comments 2024-06-30 00:33:08 -06:00
Mauricio Siu
54adab16cf feat(templates): add excalidraw 2024-06-30 00:32:14 -06:00
Mauricio Siu
2e3b0ddcde feat(templates): add appsmith 2024-06-30 00:24:16 -06:00
Mauricio Siu
ed0f3cadd6 feat(templates): add odoo template 2024-06-29 23:38:47 -06:00
Mauricio Siu
898880634a feat(template): add wordpress 2024-06-29 19:56:59 -06:00
Mauricio Siu
b4e154fb28 Merge pull request #177 from ephraimduncan/fix/template-dialog-background
fix: avoid black background on light theme
2024-06-29 19:39:48 -06:00
Mauricio Siu
2e6489d315 Merge pull request #178 from ephraimduncan/chore/remove-row-selection-count
chore: remove row selection count
2024-06-29 19:38:36 -06:00
Mauricio Siu
210fed30a2 feat(templates): add uptime kuma, directus, baserow, ghost, n8n 2024-06-29 19:14:46 -06:00
Ephraim Atta-Duncan
60521c1025 chore: remove row selection count because row selection has been disabled 2024-06-30 00:49:19 +00:00
Ephraim Atta-Duncan
1a496e35c0 fix: avoid black background on light theme 2024-06-30 00:37:20 +00:00
Ephraim Atta-Duncan
f37f98aade fix: avoid using defined secrets in calcom template 2024-06-30 00:33:49 +00:00
Ephraim Atta-Duncan
0eb7b3ecb1 chore: remove obselete version 2024-06-30 00:30:53 +00:00
Ephraim Atta-Duncan
1a7c602861 feat: add documenso as template 2024-06-30 00:28:53 +00:00
537 changed files with 53369 additions and 11508 deletions

View 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
View 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
View 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
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: [siumauricio]
patreon: #
open_collective: dokploy
ko_fi: # Replace with a single Ko-fi username

View File

@@ -1,59 +1,49 @@
name: Pull request
on:
pull_request:
branches:
- main
- canary
push:
branches:
- main
- canary
env:
HUSKY: 0
jobs:
build-app:
if: github.event_name == 'pull_request'
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
strategy:
matrix:
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:
- name: Check out the code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: actions/checkout@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
fetch-depth: 0
- name: Prepare .env file
run: |
cp .env.production.example .env.production
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Build and push Docker image using custom script
run: |
chmod +x ./docker/push.sh
./docker/push.sh ${{ github.ref_name == 'canary' && 'canary' || '' }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
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
View File

@@ -52,6 +52,7 @@ yarn-error.log*
# otros
/.data
/.main
.vscode
*.lockb
*.rdb

4
.husky/commit-msg Normal file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm commitlint --edit $1

6
.husky/install.mjs Normal file
View 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
View File

@@ -0,0 +1 @@
pnpm lint-staged

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
18.18.0

View File

@@ -1,48 +1,65 @@
# Etapa 1: Prepare image for building
FROM node:18-slim AS base
# Install dependencies
# Disable husky
ENV HUSKY=0
# Set pnpm home
ENV PNPM_HOME="/pnpm"
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
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 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
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
# Copy the rest of the source code
COPY . .
# Build the application
RUN pnpm run build
# Build the application
RUN pnpm build
# Stage 2: Prepare image for production
FROM node:18-slim AS production
FROM base AS production
# Set production
ENV NODE_ENV=production
# Install dependencies only for production
ENV PNPM_HOME="/pnpm"
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/*
RUN apt-get update && apt-get install -y curl apache2-utils && rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy the rest of the source code
COPY --from=base /app/.next ./.next
COPY --from=base /app/dist ./dist
COPY --from=base /app/next.config.mjs ./next.config.mjs
COPY --from=base /app/public ./public
COPY --from=base /app/package.json ./package.json
COPY --from=base /app/drizzle ./drizzle
COPY --from=base /app/.env.production ./.env
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
# Copy the rest of the source code
COPY --from=build /app/.next ./.next
COPY --from=build /app/dist ./dist
COPY --from=build /app/next.config.mjs ./next.config.mjs
COPY --from=build /app/public ./public
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/drizzle ./drizzle
COPY --from=build /app/.env.production ./.env
COPY --from=build /app/components.json ./components.json
COPY --from=prod-deps /app/node_modules ./node_modules
# Install docker
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 \
&& pnpm install -g tsx
# 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 3000
CMD ["pnpm", "start"]
CMD ["pnpm", "start"]

View File

@@ -1,4 +1,3 @@
<div align="center">
<h1 align="center">Dokploy</h1>
<div>
@@ -11,74 +10,67 @@
<br />
Dokploy is a free self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
### Features
Dokploy include multiples features to make your life easier.
* **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.
* **Backups**: Automate backups for databases to a external storage destination.
* **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.
* **Templates**: Deploy in a single click open source templates (Plausible, Pocketbase, Calcom, etc.).
* **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
* **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
* **Docker Management**: Easily deploy and manage Docker containers.
* **CLI/API**: Manage your applications and databases using the command line or trought the API.
* **Self-Hosted**: Self-host Dokploy on your VPS.
- **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.
- **Backups**: Automate backups for databases to a external storage destination.
- **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.
- **Templates**: Deploy in a single click open source templates (Plausible, Pocketbase, Calcom, etc.).
- **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
- **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
- **Docker Management**: Easily deploy and manage Docker containers.
- **CLI/API**: Manage your applications and databases using the command line or trought the API.
- **Self-Hosted**: Self-host Dokploy on your VPS.
## 🚀 Getting Started
To get started run the following command in a VPS:
```bash
curl -sSL https://dokploy.com/install.sh | sh
```
## 📄 Documentation
For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
## Video Tutorial
<a href="https://youtu.be/mznYKPvhcfw">
<img src="https://dokploy.com/banner.webp" alt="Watch the video" width="400" style="border-radius:20px;"/>
</a>
## 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:
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>
## Contributors
<a href="https://github.com/dokploy/dokploy/graphs/contributors">
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" />
</a>
## Support OS
- Ubuntu 24.04 LTS
- Ubuntu 24.04 LTS
- Ubuntu 23.10
- Ubuntu 22.04 LTS
- Ubuntu 20.04 LTS
- Ubuntu 22.04 LTS
- Ubuntu 20.04 LTS
- Ubuntu 18.04 LTS
- Debian 12
- Debian 11
@@ -86,9 +78,6 @@ https://opencollective.com/dokploy
- Centos 9
- Centos 8
## Explanation
[English](README.md) | [中文](README-zh.md) | [Deutsch](README-de.md) | [Русский Язык](README-ru.md)

View File

@@ -1,7 +1,7 @@
import { expect, test } from "vitest";
import { load } from "js-yaml";
import { addPrefixToAllProperties } from "@/server/utils/docker/compose";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { load } from "js-yaml";
import { expect, test } from "vitest";
const composeFile1 = `
version: "3.8"

View File

@@ -79,10 +79,11 @@ test("Add prefix to networks in services with aliases", () => {
`frontend-${prefix}`,
);
const networkConfig =
actualComposeData?.services?.api?.networks[`frontend-${prefix}`];
expect(networkConfig).toBeDefined();
expect(networkConfig?.aliases).toContain("api");
const networkConfig = actualComposeData?.services?.api?.networks as {
[key: string]: { aliases?: string[] };
};
expect(networkConfig[`frontend-${prefix}`]).toBeDefined();
expect(networkConfig[`frontend-${prefix}`]?.aliases).toContain("api");
expect(actualComposeData.services?.api?.networks).not.toHaveProperty(
"frontend-ash",
@@ -169,7 +170,9 @@ test("Add prefix to networks in services (combined case)", () => {
);
// 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[`frontend-${prefix}`]).toBeDefined();
expect(apiNetworks).not.toHaveProperty("frontend");

View File

@@ -76,9 +76,11 @@ test("Add prefix to networks in services and root (combined case)", () => {
);
// 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[`frontend-${prefix}`]?.aliases).toContain("api");
expect(apiNetworks?.[`frontend-${prefix}`]?.aliases).toContain("api");
expect(apiNetworks).not.toHaveProperty("frontend");
// Caso 3: Objeto con redes simples

View File

@@ -1,8 +1,8 @@
import { expect, test } from "vitest";
import { load, dump } from "js-yaml";
import { generateRandomHash } from "@/server/utils/docker/compose";
import type { ComposeSpecification } from "@/server/utils/docker/types";
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", () => {
const hash = generateRandomHash();

View File

@@ -42,7 +42,7 @@ test("Add prefix to service names with container_name in compose file", () => {
const actualComposeData = { ...composeData, services: updatedComposeData };
// 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}`,
);
// 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");
// 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",
);
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
"myapi:latest",
);
});

View File

@@ -51,30 +51,30 @@ test("Add prefix to service names with depends_on (array) in compose file", () =
expect(actualComposeData.services).not.toHaveProperty("web");
// 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",
);
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
"myapi:latest",
);
// 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}`,
);
expect(actualComposeData.services[`web-${prefix}`].depends_on).toContain(
expect(actualComposeData.services?.[`web-${prefix}`]?.depends_on).toContain(
`api-${prefix}`,
);
// Verificar que los servicios `db` y `api` también tienen el prefijo
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
expect(actualComposeData.services).not.toHaveProperty("db");
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
"postgres:latest",
);
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
expect(actualComposeData.services).not.toHaveProperty("api");
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
"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");
// 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",
);
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
"myapi:latest",
);
// Verificar que los nombres en depends_on tienen el prefijo
const webDependsOn = actualComposeData.services[`web-${prefix}`]
.depends_on as Record<string, any>;
const webDependsOn = actualComposeData.services?.[`web-${prefix}`]
?.depends_on as Record<string, any>;
expect(webDependsOn).toHaveProperty(`db-${prefix}`);
expect(webDependsOn).toHaveProperty(`api-${prefix}`);
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
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
expect(actualComposeData.services).not.toHaveProperty("db");
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
"postgres:latest",
);
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
expect(actualComposeData.services).not.toHaveProperty("api");
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
"myapi:latest",
);
});

View File

@@ -49,22 +49,22 @@ test("Add prefix to service names with extends (string) in compose file", () =>
expect(actualComposeData.services).not.toHaveProperty("web");
// 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",
);
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
"myapi:latest",
);
// 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}`,
);
// Verificar que el servicio `base_service` también tiene el prefijo
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
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",
);
});
@@ -109,23 +109,23 @@ test("Add prefix to service names with extends (object) in compose file", () =>
expect(actualComposeData.services).not.toHaveProperty("web");
// 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",
);
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
"myapi:latest",
);
// 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") {
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
expect(actualComposeData.services).toHaveProperty(`base_service-${prefix}`);
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",
);
});

View File

@@ -50,27 +50,27 @@ test("Add prefix to service names with links in compose file", () => {
expect(actualComposeData.services).not.toHaveProperty("web");
// 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",
);
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
"myapi:latest",
);
// 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}`,
);
// Verificar que los servicios `db` y `api` también tienen el prefijo
expect(actualComposeData.services).toHaveProperty(`db-${prefix}`);
expect(actualComposeData.services).not.toHaveProperty("db");
expect(actualComposeData.services[`db-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`db-${prefix}`]?.image).toBe(
"postgres:latest",
);
expect(actualComposeData.services).toHaveProperty(`api-${prefix}`);
expect(actualComposeData.services).not.toHaveProperty("api");
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
"myapi:latest",
);
});

View File

@@ -54,23 +54,25 @@ test("Add prefix to service names with volumes_from in compose file", () => {
expect(actualComposeData.services).not.toHaveProperty("web");
// 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",
);
expect(actualComposeData.services[`api-${prefix}`].image).toBe(
expect(actualComposeData.services?.[`api-${prefix}`]?.image).toBe(
"myapi:latest",
);
// 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}`,
);
expect(actualComposeData.services[`api-${prefix}`].volumes_from).toContain(
expect(actualComposeData.services?.[`api-${prefix}`]?.volumes_from).toContain(
`shared-${prefix}`,
);
// Verificar que el servicio shared también tiene el prefijo
expect(actualComposeData.services).toHaveProperty(`shared-${prefix}`);
expect(actualComposeData.services).not.toHaveProperty("shared");
expect(actualComposeData.services[`shared-${prefix}`].image).toBe("busybox");
expect(actualComposeData.services?.[`shared-${prefix}`]?.image).toBe(
"busybox",
);
});

View File

@@ -1,7 +1,7 @@
import { generateRandomHash } from "@/server/utils/docker/compose";
import {
addPrefixToVolumesRoot,
addPrefixToAllVolumes,
addPrefixToVolumesRoot,
} from "@/server/utils/docker/compose/volume";
import type { ComposeSpecification } from "@/server/utils/docker/types";
import { load } from "js-yaml";

View 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);
});
});

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
Gogogogogogo

View File

@@ -0,0 +1 @@
gogogogogog

View File

@@ -0,0 +1 @@
gogogogogogogogogo

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
dsafasdfasdf

Binary file not shown.

View 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");
});

View File

@@ -1,5 +1,5 @@
import { defineConfig } from "vitest/config";
import tsconfigPaths from "vite-tsconfig-paths";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [

View File

@@ -1,17 +1,34 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"linter":{
"rules": {
"correctness":{
"useExhaustiveDependencies": "off"
},
"suspicious":{
"noArrayIndexKey": "off"
},
"a11y":{
"noSvgWithoutTitle":"off"
}
}
}
}
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"files": {
"ignore": ["node_modules/**", ".next/**", "drizzle/**", ".docker"]
},
"organizeImports": {
"enabled": true
},
"linter": {
"rules": {
"complexity": {
"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"
}
}
}
}

View File

@@ -1,17 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "styles/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "styles/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

View File

@@ -10,19 +10,19 @@ import {
} from "@/components/ui/form";
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 {
InputOTP,
InputOTPGroup,
InputOTPSlot,
} 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 { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const Login2FASchema = z.object({
pin: z.string().min(6, {

View File

@@ -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 {
Dialog,
@@ -17,21 +19,19 @@ import {
FormLabel,
FormMessage,
} 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 {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} 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
.object({

View File

@@ -1,4 +1,5 @@
import React from "react";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@@ -6,8 +7,6 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { z } from "zod";
import {
Form,
FormControl,
@@ -16,11 +15,6 @@ import {
FormLabel,
FormMessage,
} 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 {
Select,
@@ -31,10 +25,16 @@ import {
SelectTrigger,
SelectValue,
} 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 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 { AlertBlock } from "@/components/shared/alert-block";
interface Props {
applicationId: string;

View File

@@ -1,4 +1,4 @@
import React from "react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@@ -6,8 +6,6 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { z } from "zod";
import {
Form,
FormControl,
@@ -16,12 +14,14 @@ import {
FormLabel,
FormMessage,
} 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 { 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 {
applicationId: string;
}

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -17,13 +18,6 @@ import {
FormMessage,
} from "@/components/ui/form";
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 {
Select,
SelectContent,
@@ -31,6 +25,12 @@ import {
SelectTrigger,
SelectValue,
} 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";
const AddPortSchema = z.object({

View File

@@ -1,4 +1,4 @@
import React from "react";
import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -8,10 +8,10 @@ import {
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { Rss } from "lucide-react";
import React from "react";
import { AddPort } from "./add-port";
import { DeletePort } from "./delete-port";
import { UpdatePort } from "./update-port";
import { AlertBlock } from "@/components/shared/alert-block";
interface Props {
applicationId: string;
}

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -17,14 +18,6 @@ import {
FormMessage,
} from "@/components/ui/form";
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 {
Select,
SelectContent,
@@ -32,6 +25,13 @@ import {
SelectTrigger,
SelectValue,
} 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({
publishedPort: z.number().int().min(1).max(65535),

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -11,22 +12,21 @@ import {
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormDescription,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
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 { PlusIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { PlusIcon } from "lucide-react";
import { z } from "zod";
import { Switch } from "@/components/ui/switch";
const AddRedirectchema = z.object({
regex: z.string().min(1, "Regex required"),

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
Card,
CardContent,
@@ -8,6 +7,7 @@ import {
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { Split } from "lucide-react";
import React from "react";
import { AddRedirect } from "./add-redirect";
import { DeleteRedirect } from "./delete-redirect";
import { UpdateRedirect } from "./update-redirect";

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -11,22 +12,21 @@ import {
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormDescription,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
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, Pencil } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Switch } from "@/components/ui/switch";
const UpdateRedirectSchema = z.object({
regex: z.string().min(1, "Regex required"),
permanent: z.boolean().default(false),

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -18,12 +19,11 @@ import {
} from "@/components/ui/form";
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 { PlusIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { PlusIcon } from "lucide-react";
import { z } from "zod";
const AddSecuritychema = z.object({

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
Card,
CardContent,
@@ -8,6 +7,7 @@ import {
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { LockKeyhole } from "lucide-react";
import React from "react";
import { AddSecurity } from "./add-security";
import { DeleteSecurity } from "./delete-security";
import { UpdateSecurity } from "./update-security";

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -18,7 +19,6 @@ import {
} from "@/components/ui/form";
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";

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -21,7 +22,6 @@ import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
const addResourcesApplication = z.object({
memoryReservation: z.number().nullable().optional(),

View File

@@ -1,4 +1,4 @@
import React from "react";
import { CodeEditor } from "@/components/shared/code-editor";
import {
Card,
CardContent,
@@ -8,8 +8,8 @@ import {
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { File } from "lucide-react";
import React from "react";
import { UpdateTraefikConfig } from "./update-traefik-config";
import { CodeEditor } from "@/components/shared/code-editor";
interface Props {
applicationId: string;
}
@@ -29,7 +29,7 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
<CardTitle className="text-xl">Traefik</CardTitle>
<CardDescription>
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
</CardDescription>
</div>

View File

@@ -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 {
Dialog,
@@ -17,14 +19,12 @@ import {
FormMessage,
} from "@/components/ui/form";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import jsyaml from "js-yaml";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import jsyaml from "js-yaml";
import { CodeEditor } from "@/components/shared/code-editor";
const UpdateTraefikConfigSchema = z.object({
traefikConfig: z.string(),
@@ -110,12 +110,15 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
};
return (
<Dialog open={open} onOpenChange={(open) => {
setOpen(open)
if (!open) {
form.reset();
}
}}>
<Dialog
open={open}
onOpenChange={(open) => {
setOpen(open);
if (!open) {
form.reset();
}
}}
>
<DialogTrigger asChild>
<Button isLoading={isLoading}>Modify</Button>
</DialogTrigger>

View File

@@ -1,8 +1,4 @@
import { zodResolver } from "@hookform/resolvers/zod";
import type React from "react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
@@ -22,12 +18,16 @@ import {
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
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 { api } from "@/utils/api";
import { toast } from "sonner";
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 {
serviceId: string;
serviceType:
@@ -63,6 +63,7 @@ const mySchema = z.discriminatedUnion("type", [
z
.object({
type: z.literal("file"),
filePath: z.string().min(1, "File path required"),
content: z.string().optional(),
})
.merge(mountSchema),
@@ -81,7 +82,7 @@ export const AddVolumes = ({
defaultValues: {
type: serviceType === "compose" ? "file" : "bind",
hostPath: "",
mountPath: "",
mountPath: serviceType === "compose" ? "/" : "",
},
resolver: zodResolver(mySchema),
});
@@ -125,6 +126,7 @@ export const AddVolumes = ({
serviceId,
content: data.content,
mountPath: data.mountPath,
filePath: data.filePath,
type: data.type,
serviceType,
})
@@ -288,41 +290,62 @@ export const AddVolumes = ({
)}
{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
control={form.control}
name="content"
name="mountPath"
render={({ field }) => (
<FormItem>
<FormLabel>Content</FormLabel>
<FormLabel>Mount Path (In the container)</FormLabel>
<FormControl>
<FormControl>
<Textarea
placeholder="Any content"
className="h-64"
{...field}
/>
</FormControl>
<Input placeholder="Mount Path" {...field} />
</FormControl>
<FormMessage />
</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>
</form>

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
AlertDialog,
AlertDialogAction,
@@ -13,6 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { TrashIcon } from "lucide-react";
import React from "react";
import { toast } from "sonner";
interface Props {

View File

@@ -1,4 +1,4 @@
import React from "react";
import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -8,10 +8,10 @@ import {
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { AlertTriangle, Package } from "lucide-react";
import React from "react";
import { AddVolumes } from "./add-volumes";
import { DeleteVolume } from "./delete-volume";
import { UpdateVolume } from "./update-volume";
import { AlertBlock } from "@/components/shared/alert-block";
interface Props {
applicationId: string;
}
@@ -73,8 +73,7 @@ export const ShowVolumes = ({ applicationId }: Props) => {
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"
>
{/* <Package className="size-8 self-center text-muted-foreground" /> */}
<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">
<span className="font-medium">Mount Type</span>
<span className="text-sm text-muted-foreground">
@@ -91,12 +90,21 @@ export const ShowVolumes = ({ applicationId }: Props) => {
)}
{mount.type === "file" && (
<div className="flex flex-col gap-1">
<span className="font-medium">Content</span>
<span className="text-sm text-muted-foreground">
{mount.content}
</span>
</div>
<>
<div className="flex flex-col gap-1">
<span className="font-medium">Content</span>
<span className="text-sm text-muted-foreground">
{mount.content}
</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" && (
<div className="flex flex-col gap-1">
@@ -118,6 +126,7 @@ export const ShowVolumes = ({ applicationId }: Props) => {
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="application"
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
</div>

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -17,15 +18,14 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { Pencil } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Textarea } from "@/components/ui/textarea";
const mountSchema = z.object({
mountPath: z.string().min(1, "Mount path required"),
@@ -48,6 +48,7 @@ const mySchema = z.discriminatedUnion("type", [
.object({
type: z.literal("file"),
content: z.string().optional(),
filePath: z.string().min(1, "File path required"),
})
.merge(mountSchema),
]);
@@ -58,9 +59,23 @@ interface Props {
mountId: string;
type: "bind" | "volume" | "file";
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 { data } = api.mounts.one.useQuery(
{
@@ -103,6 +118,7 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
form.reset({
content: data.content || "",
mountPath: data.mountPath,
filePath: data.filePath || "",
type: "file",
});
}
@@ -141,6 +157,7 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
content: data.content,
mountPath: data.mountPath,
type: data.type,
filePath: data.filePath,
mountId,
})
.then(() => {
@@ -166,6 +183,11 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
<DialogDescription>Update the mount</DialogDescription>
</DialogHeader>
{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
@@ -211,40 +233,62 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
)}
{type === "file" && (
<FormField
control={form.control}
name="content"
render={({ field }) => (
<FormItem>
<FormLabel>Content</FormLabel>
<FormControl>
<>
<FormField
control={form.control}
name="content"
render={({ field }) => (
<FormItem>
<FormLabel>Content</FormLabel>
<FormControl>
<Textarea
placeholder="Any content"
className="h-64"
<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>
<Input
disabled
placeholder="Name of the file"
{...field}
/>
</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>
<FormMessage />
</FormItem>
)}
/>
)}
<FormField
control={form.control}
name="mountPath"
render={({ field }) => (
<FormItem>
<FormLabel>Mount Path</FormLabel>
<FormControl>
<Input placeholder="Mount Path" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<DialogFooter>
<Button

View File

@@ -1,213 +1,213 @@
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
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 { zodResolver } from "@hookform/resolvers/zod";
import { Cog } from "lucide-react";
import { useEffect } from "react";
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 { Input } from "@/components/ui/input";
import { z } from "zod";
enum BuildType {
dockerfile = "dockerfile",
heroku_buildpacks = "heroku_buildpacks",
paketo_buildpacks = "paketo_buildpacks",
nixpacks = "nixpacks",
dockerfile = "dockerfile",
heroku_buildpacks = "heroku_buildpacks",
paketo_buildpacks = "paketo_buildpacks",
nixpacks = "nixpacks",
}
const mySchema = z.discriminatedUnion("buildType", [
z.object({
buildType: z.literal("dockerfile"),
dockerfile: z
.string({
required_error: "Dockerfile path is required",
invalid_type_error: "Dockerfile path is required",
})
.min(1, "Dockerfile required"),
}),
z.object({
buildType: z.literal("heroku_buildpacks"),
}),
z.object({
buildType: z.literal("paketo_buildpacks"),
}),
z.object({
buildType: z.literal("nixpacks"),
}),
z.object({
buildType: z.literal("dockerfile"),
dockerfile: z
.string({
required_error: "Dockerfile path is required",
invalid_type_error: "Dockerfile path is required",
})
.min(1, "Dockerfile required"),
}),
z.object({
buildType: z.literal("heroku_buildpacks"),
}),
z.object({
buildType: z.literal("paketo_buildpacks"),
}),
z.object({
buildType: z.literal("nixpacks"),
}),
]);
type AddTemplate = z.infer<typeof mySchema>;
interface Props {
applicationId: string;
applicationId: string;
}
export const ShowBuildChooseForm = ({ applicationId }: Props) => {
const { mutateAsync, isLoading } =
api.application.saveBuildType.useMutation();
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
},
{
enabled: !!applicationId,
},
);
const { mutateAsync, isLoading } =
api.application.saveBuildType.useMutation();
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
},
{
enabled: !!applicationId,
},
);
const form = useForm<AddTemplate>({
defaultValues: {
buildType: BuildType.nixpacks,
},
resolver: zodResolver(mySchema),
});
const form = useForm<AddTemplate>({
defaultValues: {
buildType: BuildType.nixpacks,
},
resolver: zodResolver(mySchema),
});
const buildType = form.watch("buildType");
useEffect(() => {
if (data) {
// TODO: refactor this
if (data.buildType === "dockerfile") {
form.reset({
buildType: data.buildType,
...(data.buildType && {
dockerfile: data.dockerfile || "",
}),
});
} else {
form.reset({
buildType: data.buildType,
});
}
}
}, [form.formState.isSubmitSuccessful, form.reset, data, form]);
const buildType = form.watch("buildType");
useEffect(() => {
if (data) {
// TODO: refactor this
if (data.buildType === "dockerfile") {
form.reset({
buildType: data.buildType,
...(data.buildType && {
dockerfile: data.dockerfile || "",
}),
});
} else {
form.reset({
buildType: data.buildType,
});
}
}
}, [form.formState.isSubmitSuccessful, form.reset, data, form]);
const onSubmit = async (data: AddTemplate) => {
await mutateAsync({
applicationId,
buildType: data.buildType,
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
})
.then(async () => {
toast.success("Build type saved");
await refetch();
})
.catch(() => {
toast.error("Error to save the build type");
});
};
const onSubmit = async (data: AddTemplate) => {
await mutateAsync({
applicationId,
buildType: data.buildType,
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
})
.then(async () => {
toast.success("Build type saved");
await refetch();
})
.catch(() => {
toast.error("Error to save the build type");
});
};
return (
<Card className="group relative w-full bg-transparent">
<CardHeader>
<CardTitle className="flex items-start justify-between">
<div className="flex flex-col gap-2">
<span className="flex flex-col space-y-0.5">Build Type</span>
<p className="flex items-center text-sm font-normal text-muted-foreground">
Select the way of building your code
</p>
</div>
<div className="hidden space-y-1 text-sm font-normal md:block">
<Cog className="size-6 text-muted-foreground" />
</div>
</CardTitle>
</CardHeader>
<CardContent>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4 p-2"
>
<FormField
control={form.control}
name="buildType"
defaultValue={form.control._defaultValues.buildType}
render={({ field }) => {
return (
<FormItem className="space-y-3">
<FormLabel>Build Type</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-col space-y-1"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="dockerfile" />
</FormControl>
<FormLabel className="font-normal">
Dockerfile
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="nixpacks" />
</FormControl>
<FormLabel className="font-normal">
Nixpacks
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="heroku_buildpacks" />
</FormControl>
<FormLabel className="font-normal">
Heroku Buildpacks
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="paketo_buildpacks" />
</FormControl>
<FormLabel className="font-normal">
Paketo Buildpacks
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
{buildType === "dockerfile" && (
<FormField
control={form.control}
name="dockerfile"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Docker File</FormLabel>
<FormControl>
<Input
placeholder={"Path of your docker file"}
{...field}
value={field.value ?? ""}
/>
</FormControl>
return (
<Card className="group relative w-full bg-transparent">
<CardHeader>
<CardTitle className="flex items-start justify-between">
<div className="flex flex-col gap-2">
<span className="flex flex-col space-y-0.5">Build Type</span>
<p className="flex items-center text-sm font-normal text-muted-foreground">
Select the way of building your code
</p>
</div>
<div className="hidden space-y-1 text-sm font-normal md:block">
<Cog className="size-6 text-muted-foreground" />
</div>
</CardTitle>
</CardHeader>
<CardContent>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4 p-2"
>
<FormField
control={form.control}
name="buildType"
defaultValue={form.control._defaultValues.buildType}
render={({ field }) => {
return (
<FormItem className="space-y-3">
<FormLabel>Build Type</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-col space-y-1"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="dockerfile" />
</FormControl>
<FormLabel className="font-normal">
Dockerfile
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="nixpacks" />
</FormControl>
<FormLabel className="font-normal">
Nixpacks
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="heroku_buildpacks" />
</FormControl>
<FormLabel className="font-normal">
Heroku Buildpacks
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="paketo_buildpacks" />
</FormControl>
<FormLabel className="font-normal">
Paketo Buildpacks
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
{buildType === "dockerfile" && (
<FormField
control={form.control}
name="dockerfile"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Docker File</FormLabel>
<FormControl>
<Input
placeholder={"Path of your docker file"}
{...field}
value={field.value ?? ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
)}
<div className="flex w-full justify-end">
<Button isLoading={isLoading} type="submit">
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
);
<FormMessage />
</FormItem>
);
}}
/>
)}
<div className="flex w-full justify-end">
<Button isLoading={isLoading} type="submit">
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
);
};

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
AlertDialog,
AlertDialogAction,
@@ -12,6 +11,7 @@ import {
} from "@/components/ui/alert-dialog";
import { api } from "@/utils/api";
import { RefreshCcw } from "lucide-react";
import React from "react";
import { toast } from "sonner";
interface Props {
@@ -29,8 +29,8 @@ export const RefreshToken = ({ applicationId }: Props) => {
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
domain
This action cannot be undone. This will change the refresh token and
other tokens will be invalidated.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>

View File

@@ -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 {
Card,
@@ -10,10 +12,8 @@ import { api } from "@/utils/api";
import { RocketIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
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 { ShowDeployment } from "./show-deployment";
interface Props {
applicationId: string;

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -27,85 +28,103 @@ import {
} 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 { PlusIcon } from "lucide-react";
import { useEffect } from "react";
import { useEffect, useState } 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,}$/;
// .regex(hostnameRegex
const addDomain = z.object({
host: z.string().min(1, "Hostname is required"),
path: z.string().min(1),
port: z.number(),
https: z.boolean(),
certificateType: z.enum(["letsencrypt", "none"]),
});
import { domain } from "@/server/db/validations";
import { zodResolver } from "@hookform/resolvers/zod";
import type z from "zod";
type AddDomain = z.infer<typeof addDomain>;
type Domain = z.infer<typeof domain>;
interface Props {
applicationId: string;
children?: React.ReactNode;
domainId?: string;
children: React.ReactNode;
}
export const AddDomain = ({
applicationId,
children = <PlusIcon className="h-4 w-4" />,
domainId = "",
children,
}: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { mutateAsync, isError, error } = api.domain.create.useMutation();
const form = useForm<AddDomain>({
defaultValues: {
host: "",
https: false,
path: "/",
port: 3000,
certificateType: "none",
const { data, refetch } = api.domain.one.useQuery(
{
domainId,
},
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(() => {
form.reset();
}, [form, form.reset, form.formState.isSubmitSuccessful]);
if (data) {
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({
domainId,
applicationId,
host: data.host,
https: data.https,
path: data.path,
port: data.port,
certificateType: data.certificateType,
...data,
})
.then(async () => {
toast.success("Domain Created");
toast.success(dictionary.success);
await utils.domain.byApplicationId.invalidate({
applicationId,
});
await utils.application.readTraefikConfig.invalidate({ applicationId });
if (domainId) {
refetch();
}
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the domain");
toast.error(dictionary.error);
});
};
return (
<Dialog>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger className="" asChild>
<Button>{children}</Button>
{children}
</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>
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
@@ -169,33 +188,36 @@ export const AddDomain = ({
);
}}
/>
<FormField
control={form.control}
name="certificateType"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Certificate</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a certificate" />
</SelectTrigger>
</FormControl>
{form.getValues().https && (
<FormField
control={form.control}
name="certificateType"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Certificate</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a certificate" />
</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
control={form.control}
name="https"
@@ -206,6 +228,7 @@ export const AddDomain = ({
<FormDescription>
Automatically provision SSL Certificate.
</FormDescription>
<FormMessage />
</div>
<FormControl>
<Switch
@@ -226,7 +249,7 @@ export const AddDomain = ({
form="hook-form"
type="submit"
>
Create
{dictionary.submit}
</Button>
</DialogFooter>
</Form>

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
AlertDialog,
AlertDialogAction,
@@ -13,6 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { TrashIcon } from "lucide-react";
import React from "react";
import { toast } from "sonner";
interface Props {

View File

@@ -7,11 +7,11 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { api } from "@/utils/api";
import { RefreshCcw } from "lucide-react";
import Link from "next/link";
import { GenerateTraefikMe } from "./generate-traefikme";
import { GenerateWildCard } from "./generate-wildcard";
import Link from "next/link";
import { api } from "@/utils/api";
interface Props {
applicationId: string;

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
AlertDialog,
AlertDialogAction,
@@ -13,6 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { RefreshCcw } from "lucide-react";
import React from "react";
import { toast } from "sonner";
interface Props {

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
AlertDialog,
AlertDialogAction,
@@ -13,6 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { SquareAsterisk } from "lucide-react";
import React from "react";
import { toast } from "sonner";
interface Props {

View File

@@ -1,4 +1,4 @@
import React from "react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@@ -6,14 +6,12 @@ import {
CardHeader,
CardTitle,
} 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 { DeleteDomain } from "./delete-domain";
import { api } from "@/utils/api";
import { ExternalLink, GlobeIcon, PenBoxIcon } from "lucide-react";
import Link from "next/link";
import { AddDomain } from "./add-domain";
import { UpdateDomain } from "./update-domain";
import { DeleteDomain } from "./delete-domain";
import { GenerateDomain } from "./generate-domain";
interface Props {
@@ -43,7 +41,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
<div className="flex flex-row gap-4 flex-wrap">
{data && data?.length > 0 && (
<AddDomain applicationId={applicationId}>
<GlobeIcon className="size-4" /> Add Domain
<Button>
<GlobeIcon className="size-4" /> Add Domain
</Button>
</AddDomain>
)}
{data && data?.length > 0 && (
@@ -61,7 +61,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
</span>
<div className="flex flex-row gap-4 flex-wrap">
<AddDomain applicationId={applicationId}>
<GlobeIcon className="size-4" /> Add Domain
<Button>
<GlobeIcon className="size-4" /> Add Domain
</Button>
</AddDomain>
<GenerateDomain applicationId={applicationId} />
@@ -90,7 +92,14 @@ export const ShowDomains = ({ applicationId }: Props) => {
{item.https ? "HTTPS" : "HTTP"}
</Button>
<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} />
</div>
</div>

View File

@@ -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>
);
};

View File

@@ -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 { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Card, CardContent } from "@/components/ui/card";
import { Form } from "@/components/ui/form";
import { Secrets } from "@/components/ui/secrets";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { Toggle } from "@/components/ui/toggle";
import { CodeEditor } from "@/components/shared/code-editor";
import { EyeIcon, EyeOffIcon } from "lucide-react";
import { z } from "zod";
const addEnvironmentSchema = z.object({
environment: z.string(),
env: z.string(),
buildArgs: z.string(),
});
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
@@ -34,7 +20,6 @@ interface Props {
}
export const ShowEnvironment = ({ applicationId }: Props) => {
const [isEnvVisible, setIsEnvVisible] = useState(true);
const { mutateAsync, isLoading } =
api.application.saveEnvironment.useMutation();
@@ -46,24 +31,19 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
enabled: !!applicationId,
},
);
const form = useForm<EnvironmentSchema>({
defaultValues: {
environment: "",
env: data?.env || "",
buildArgs: data?.buildArgs || "",
},
resolver: zodResolver(addEnvironmentSchema),
});
useEffect(() => {
if (data) {
form.reset({
environment: data.env || "",
});
}
}, [form.reset, data, form]);
const onSubmit = async (data: EnvironmentSchema) => {
mutateAsync({
env: data.environment,
env: data.env,
buildArgs: data.buildArgs,
applicationId,
})
.then(async () => {
@@ -74,94 +54,50 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
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 (
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
<CardHeader className="flex flex-row w-full items-center justify-between">
<div>
<CardTitle className="text-xl">Environment Settings</CardTitle>
<CardDescription>
You can add environment variables to your resource.
</CardDescription>
</div>
<Toggle
aria-label="Toggle bold"
pressed={isEnvVisible}
onPressedChange={setIsEnvVisible}
>
{isEnvVisible ? (
<EyeOffIcon className="h-4 w-4 text-muted-foreground" />
) : (
<EyeIcon className="h-4 w-4 text-muted-foreground" />
)}
</Toggle>
</CardHeader>
<CardContent>
<Form {...form}>
<form
id="hook-form"
onSubmit={form.handleSubmit(onSubmit)}
className="w-full space-y-4"
>
<FormField
control={form.control}
name="environment"
render={({ field }) => (
<FormItem className="w-full">
<FormControl>
<CodeEditor
language="properties"
disabled={isEnvVisible}
placeholder={`NODE_ENV=production
PORT=3000
`}
className="h-96 font-mono"
{...field}
/>
</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>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex w-full flex-col gap-5 "
>
<Card className="bg-background">
<Secrets
name="env"
title="Environment Settings"
description="You can add environment variables to your resource."
placeholder={["NODE_ENV=production", "PORT=3000"].join("\n")}
/>
{data?.buildType === "dockerfile" && (
<Secrets
name="buildArgs"
title="Build-time Variables"
description={
<span>
Available only at build-time. See documentation&nbsp;
<a
className="text-primary"
href="https://docs.docker.com/build/guide/build-args/"
target="_blank"
rel="noopener noreferrer"
>
here
</a>
.
</span>
}
placeholder="NPM_TOKEN=xyz"
/>
)}
<CardContent>
<div className="flex flex-row justify-end">
<Button isLoading={isLoading} className="w-fit" type="submit">
Save
</Button>
</div>
</CardContent>
</Card>
</form>
</Form>
);
};

View File

@@ -11,7 +11,6 @@ import {
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { useEffect, useState } from "react";
import { toast } from "sonner";
interface Props {
@@ -26,8 +25,6 @@ export const DeployApplication = ({ applicationId }: Props) => {
{ enabled: !!applicationId },
);
const { mutateAsync: markRunning } =
api.application.markRunning.useMutation();
const { mutateAsync: deploy } = api.application.deploy.useMutation();
return (
@@ -48,24 +45,16 @@ export const DeployApplication = ({ applicationId }: Props) => {
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await markRunning({
toast.success("Deploying Application....");
await refetch();
await deploy({
applicationId,
})
.then(async () => {
toast.success("Deploying Application....");
}).catch(() => {
toast.error("Error to deploy Application");
});
await refetch();
await deploy({
applicationId,
}).catch(() => {
toast.error("Error to deploy Application");
});
await refetch();
})
.catch((e) => {
toast.error(e.message || "Error to deploy Application");
});
await refetch();
}}
>
Confirm

View File

@@ -1,8 +1,4 @@
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 {
Form,
FormControl,
@@ -11,9 +7,13 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { useEffect } from "react";
import { Input } from "@/components/ui/input";
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 { z } from "zod";
const DockerProviderSchema = z.object({
dockerImage: z.string().min(1, {

View File

@@ -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>
);
};

View File

@@ -1,13 +1,4 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
@@ -17,11 +8,20 @@ import {
FormMessage,
} from "@/components/ui/form";
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 { zodResolver } from "@hookform/resolvers/zod";
import copy from "copy-to-clipboard";
import { CopyIcon, LockIcon } from "lucide-react";
import { KeyRoundIcon, LockIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -33,6 +33,7 @@ const GitProviderSchema = z.object({
}),
branch: z.string().min(1, "Branch required"),
buildPath: z.string().min(1, "Build Path required"),
sshKey: z.string().optional(),
});
type GitProvider = z.infer<typeof GitProviderSchema>;
@@ -43,19 +44,18 @@ interface Props {
export const SaveGitProvider = ({ applicationId }: Props) => {
const { data, refetch } = api.application.one.useQuery({ applicationId });
const { data: sshKeys } = api.sshKey.all.useQuery();
const router = useRouter();
const { mutateAsync, isLoading } =
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>({
defaultValues: {
branch: "",
buildPath: "/",
repositoryURL: "",
sshKey: undefined,
},
resolver: zodResolver(GitProviderSchema),
});
@@ -63,6 +63,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
useEffect(() => {
if (data) {
form.reset({
sshKey: data.customGitSSHKeyId || undefined,
branch: data.customGitBranch || "",
buildPath: data.customGitBuildPath || "/",
repositoryURL: data.customGitUrl || "",
@@ -75,6 +76,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
customGitBranch: values.branch,
customGitBuildPath: values.buildPath,
customGitUrl: values.repositoryURL,
customGitSSHKeyId: values.sshKey === "none" ? null : values.sshKey,
applicationId,
})
.then(async () => {
@@ -92,160 +94,103 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
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="repositoryURL"
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row justify-between">
Repository URL
<div className="flex gap-2">
<Dialog>
<DialogTrigger className="flex flex-row gap-2">
<LockIcon className="size-4 text-muted-foreground" />?
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Private Repository</DialogTitle>
<DialogDescription>
If your repository is private is necessary to
generate SSH Keys to add to your git provider.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="relative">
<Textarea
placeholder="Please click on Generate SSH Key"
className="no-scrollbar h-64 text-muted-foreground"
disabled={!data?.customGitSSHKey}
contentEditable={false}
value={
data?.customGitSSHKey ||
"Please click on Generate SSH Key"
}
/>
<button
type="button"
className="absolute right-2 top-2"
onClick={() => {
copy(
data?.customGitSSHKey ||
"Generate a SSH Key",
);
toast.success("SSH Copied to clipboard");
}}
<div className="grid md:grid-cols-2 gap-4">
<div className="flex items-end col-span-2 gap-4">
<div className="grow">
<FormField
control={form.control}
name="repositoryURL"
render={({ field }) => (
<FormItem>
<FormLabel>Repository URL</FormLabel>
<FormControl>
<Input placeholder="git@bitbucket.org" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{sshKeys && sshKeys.length > 0 ? (
<FormField
control={form.control}
name="sshKey"
render={({ field }) => (
<FormItem className="basis-40">
<FormLabel className="w-full inline-flex justify-between">
SSH Key
<LockIcon className="size-4 text-muted-foreground" />
</FormLabel>
<FormControl>
<Select
key={field.value}
onValueChange={field.onChange}
defaultValue={field.value}
value={field.value}
>
<SelectTrigger>
<SelectValue placeholder="Select a key" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{sshKeys?.map((sshKey) => (
<SelectItem
key={sshKey.sshKeyId}
value={sshKey.sshKeyId}
>
<CopyIcon className="size-4" />
</button>
</div>
</div>
<DialogFooter className="flex sm:justify-between gap-3.5 flex-col sm:flex-col w-full">
<div className="flex flex-row gap-2 w-full justify-between flex-wrap">
{data?.customGitSSHKey && (
<Button
variant="destructive"
isLoading={
isGeneratingSSHKey || isRemovingSSHKey
}
className="max-sm:w-full"
onClick={async () => {
await removeSSHKey({
applicationId,
})
.then(async () => {
toast.success("SSH Key Removed");
await refetch();
})
.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>
)}
/>
{sshKey.name}
</SelectItem>
))}
<SelectItem value="none">None</SelectItem>
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
</FormItem>
)}
/>
) : (
<Button
variant="secondary"
onClick={() => router.push("/dashboard/settings/ssh-keys")}
type="button"
>
<KeyRoundIcon className="size-4" /> Add SSH Key
</Button>
)}
</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 className="flex flex-row justify-end">
<Button type="submit" className="w-fit" isLoading={isLoading}>
Save{" "}
Save
</Button>
</div>
</form>

View File

@@ -7,8 +7,9 @@ import { api } from "@/utils/api";
import { GitBranch, LockIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { SaveDragNDrop } from "./save-drag-n-drop";
type TabState = "github" | "docker" | "git";
type TabState = "github" | "docker" | "git" | "drop";
interface Props {
applicationId: string;
@@ -62,6 +63,12 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
>
Git
</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>
<TabsContent value="github" className="w-full p-2">
{haveGithubConfigured ? (
@@ -89,6 +96,9 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
<TabsContent value="git" className="w-full p-2">
<SaveGitProvider applicationId={applicationId} />
</TabsContent>
<TabsContent value="drop" className="w-full p-2">
<SaveDragNDrop applicationId={applicationId} />
</TabsContent>
</Tabs>
</CardContent>
</Card>

View File

@@ -4,13 +4,13 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Toggle } from "@/components/ui/toggle";
import { api } from "@/utils/api";
import { CheckCircle2, Terminal } from "lucide-react";
import React from "react";
import { toast } from "sonner";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
import { RedbuildApplication } from "../rebuild-application";
import { StartApplication } from "../start-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 { ResetApplication } from "./reset-application";
interface Props {

View File

@@ -1,4 +1,3 @@
import dynamic from "next/dynamic";
import {
Card,
CardContent,
@@ -6,6 +5,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
@@ -16,8 +16,8 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
import { Label } from "@/components/ui/label";
export const DockerLogs = dynamic(
() =>
import("@/components/dashboard/docker/logs/docker-logs-id").then(

View File

@@ -25,8 +25,7 @@ export const RedbuildApplication = ({ applicationId }: Props) => {
},
{ enabled: !!applicationId },
);
const { mutateAsync: markRunning } =
api.application.markRunning.useMutation();
const { mutateAsync } = api.application.redeploy.useMutation();
const utils = api.useUtils();
return (
@@ -54,22 +53,14 @@ export const RedbuildApplication = ({ applicationId }: Props) => {
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await markRunning({
toast.success("Redeploying Application....");
await mutateAsync({
applicationId,
})
.then(async () => {
await mutateAsync({
await utils.application.one.invalidate({
applicationId,
})
.then(async () => {
await utils.application.one.invalidate({
applicationId,
});
toast.success("Application rebuild succesfully");
})
.catch(() => {
toast.error("Error to rebuild the application");
});
});
})
.catch(() => {
toast.error("Error to rebuild the application");

View File

@@ -1,3 +1,5 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
@@ -7,7 +9,6 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
@@ -16,16 +17,15 @@ import {
FormLabel,
FormMessage,
} 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 { AlertTriangle, SquarePen } from "lucide-react";
import { Textarea } from "@/components/ui/textarea";
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({
name: z.string().min(1, {

View File

@@ -1,4 +1,4 @@
import React from "react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@@ -6,8 +6,6 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { z } from "zod";
import {
Form,
FormControl,
@@ -17,12 +15,14 @@ import {
FormLabel,
FormMessage,
} 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 { 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 {
composeId: string;
}

View File

@@ -1,4 +1,4 @@
import React from "react";
import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -8,10 +8,10 @@ import {
} from "@/components/ui/card";
import { api } from "@/utils/api";
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 { DeleteVolume } from "../../application/advanced/volumes/delete-volume";
import { UpdateVolume } from "../../application/advanced/volumes/update-volume";
import { AlertBlock } from "@/components/shared/alert-block";
interface Props {
composeId: string;
}
@@ -74,7 +74,7 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
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"
>
<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">
<span className="font-medium">Mount Type</span>
<span className="text-sm text-muted-foreground">
@@ -91,12 +91,20 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
)}
{mount.type === "file" && (
<div className="flex flex-col gap-1">
<span className="font-medium">Content</span>
<span className="text-sm text-muted-foreground w-40 truncate">
{mount.content}
</span>
</div>
<>
<div className="flex flex-col gap-1">
<span className="font-medium">Content</span>
<span className="text-sm text-muted-foreground w-40 truncate">
{mount.content}
</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" && (
<div className="flex flex-col gap-1">
@@ -118,6 +126,7 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="compose"
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
</div>

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
AlertDialog,
AlertDialogAction,
@@ -12,6 +11,7 @@ import {
} from "@/components/ui/alert-dialog";
import { api } from "@/utils/api";
import { RefreshCcw } from "lucide-react";
import React from "react";
import { toast } from "sonner";
interface Props {

View File

@@ -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 {
Card,
@@ -9,14 +11,9 @@ import {
import { api } from "@/utils/api";
import { RocketIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
// import { CancelQueues } from "./cancel-queues";
// import { ShowDeployment } from "./show-deployment-compose";
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 { RefreshToken } from "./refresh-token";//
import { RefreshTokenCompose } from "./refresh-token-compose";
import { ShowDeploymentCompose } from "./show-deployment-compose";
interface Props {
composeId: string;
@@ -90,6 +87,11 @@ export const ShowDeploymentsCompose = ({ composeId }: Props) => {
<span className="text-sm text-muted-foreground">
{deployment.title}
</span>
{deployment.description && (
<span className="text-sm text-muted-foreground">
{deployment.description}
</span>
)}
</div>
<div className="flex flex-col items-end gap-2">
<div className="text-sm capitalize text-muted-foreground">

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useState } from "react";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@@ -6,10 +7,6 @@ import {
CardHeader,
CardTitle,
} 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 {
Form,
FormControl,
@@ -17,11 +14,14 @@ import {
FormItem,
FormMessage,
} 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 { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
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({
environment: z.string(),

View File

@@ -1,12 +1,4 @@
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 {
DropdownMenu,
DropdownMenuContent,
@@ -16,7 +8,15 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} 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 { 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 {
composeId: string;
@@ -50,7 +50,6 @@ export const ComposeActions = ({ composeId }: Props) => {
return (
<div className="flex flex-row gap-4 w-full flex-wrap ">
<DeployCompose composeId={composeId} />
<Toggle
aria-label="Toggle italic"
pressed={data?.autoDeploy || false}
@@ -67,8 +66,9 @@ export const ComposeActions = ({ composeId }: Props) => {
toast.error("Error to update Auto Deploy");
});
}}
className="flex flex-row gap-2 items-center"
>
Autodeploy
Autodeploy {data?.autoDeploy && <CheckCircle2 className="size-4" />}
</Toggle>
<RedbuildCompose composeId={composeId} />
{data?.composeType === "docker-compose" && (

View File

@@ -1,5 +1,5 @@
import { api } from "@/utils/api";
import { useEffect } from "react";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
@@ -7,14 +7,14 @@ import {
FormItem,
FormMessage,
} from "@/components/ui/form";
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 { z } from "zod";
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 { CodeEditor } from "@/components/shared/code-editor";
interface Props {
composeId: string;

View File

@@ -25,7 +25,6 @@ export const DeployCompose = ({ composeId }: Props) => {
{ enabled: !!composeId },
);
const { mutateAsync: markRunning } = api.compose.update.useMutation();
const { mutateAsync: deploy } = api.compose.deploy.useMutation();
return (
@@ -44,25 +43,16 @@ export const DeployCompose = ({ composeId }: Props) => {
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await markRunning({
toast.success("Deploying Compose....");
await refetch();
await deploy({
composeId,
composeStatus: "running",
})
.then(async () => {
toast.success("Deploying Compose....");
}).catch(() => {
toast.error("Error to deploy Compose");
});
await refetch();
await deploy({
composeId,
}).catch(() => {
toast.error("Error to deploy Compose");
});
await refetch();
})
.catch((e) => {
toast.error(e.message || "Error to deploy Compose");
});
await refetch();
}}
>
Confirm

View File

@@ -1,13 +1,4 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
@@ -17,11 +8,19 @@ import {
FormMessage,
} from "@/components/ui/form";
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 { zodResolver } from "@hookform/resolvers/zod";
import copy from "copy-to-clipboard";
import { CopyIcon, LockIcon } from "lucide-react";
import { KeyRoundIcon, LockIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -33,6 +32,7 @@ const GitProviderSchema = z.object({
message: "Repository URL is required",
}),
branch: z.string().min(1, "Branch required"),
sshKey: z.string().optional(),
});
type GitProvider = z.infer<typeof GitProviderSchema>;
@@ -43,19 +43,17 @@ interface Props {
export const SaveGitProviderCompose = ({ composeId }: Props) => {
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: generateSSHKey, isLoading: isGeneratingSSHKey } =
api.compose.generateSSHKey.useMutation();
const { mutateAsync: removeSSHKey, isLoading: isRemovingSSHKey } =
api.compose.removeSSHKey.useMutation();
const form = useForm<GitProvider>({
defaultValues: {
branch: "",
repositoryURL: "",
composePath: "./docker-compose.yml",
sshKey: undefined,
},
resolver: zodResolver(GitProviderSchema),
});
@@ -63,6 +61,7 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
useEffect(() => {
if (data) {
form.reset({
sshKey: data.customGitSSHKeyId || undefined,
branch: data.customGitBranch || "",
repositoryURL: data.customGitUrl || "",
composePath: data.composePath,
@@ -74,6 +73,7 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
await mutateAsync({
customGitBranch: values.branch,
customGitUrl: values.repositoryURL,
customGitSSHKeyId: values.sshKey === "none" ? null : values.sshKey,
composeId,
sourceType: "git",
composePath: values.composePath,
@@ -94,123 +94,72 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
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="repositoryURL"
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row justify-between">
Repository URL
<div className="flex gap-2">
<Dialog>
<DialogTrigger className="flex flex-row gap-2">
<LockIcon className="size-4 text-muted-foreground" />?
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Private Repository</DialogTitle>
<DialogDescription>
If your repository is private is necessary to
generate SSH Keys to add to your git provider.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="relative">
<Textarea
placeholder="Please click on Generate SSH Key"
className="no-scrollbar h-64 text-muted-foreground"
disabled={!data?.customGitSSHKey}
contentEditable={false}
value={
data?.customGitSSHKey ||
"Please click on Generate SSH Key"
}
/>
<button
type="button"
className="absolute right-2 top-2"
onClick={() => {
copy(
data?.customGitSSHKey ||
"Generate a SSH Key",
);
toast.success("SSH Copied to clipboard");
}}
<div className="flex items-end col-span-2 gap-4">
<div className="grow">
<FormField
control={form.control}
name="repositoryURL"
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row justify-between">
Repository URL
</FormLabel>
<FormControl>
<Input placeholder="git@bitbucket.org" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{sshKeys && sshKeys.length > 0 ? (
<FormField
control={form.control}
name="sshKey"
render={({ field }) => (
<FormItem className="basis-40">
<FormLabel className="w-full inline-flex justify-between">
SSH Key
<LockIcon className="size-4 text-muted-foreground" />
</FormLabel>
<FormControl>
<Select
key={field.value}
onValueChange={field.onChange}
defaultValue={field.value}
value={field.value}
>
<SelectTrigger>
<SelectValue placeholder="Select a key" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{sshKeys?.map((sshKey) => (
<SelectItem
key={sshKey.sshKeyId}
value={sshKey.sshKeyId}
>
<CopyIcon className="size-4" />
</button>
</div>
</div>
<DialogFooter className="flex sm:justify-between gap-3.5 flex-col sm:flex-col w-full">
<div className="flex flex-row gap-2 w-full justify-between flex-wrap">
{data?.customGitSSHKey && (
<Button
variant="destructive"
isLoading={
isGeneratingSSHKey || isRemovingSSHKey
}
className="max-sm:w-full"
onClick={async () => {
await removeSSHKey({
composeId,
})
.then(async () => {
toast.success("SSH Key Removed");
await refetch();
})
.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>
)}
/>
{sshKey.name}
</SelectItem>
))}
<SelectItem value="none">None</SelectItem>
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
</FormItem>
)}
/>
) : (
<Button
variant="secondary"
onClick={() => router.push("/dashboard/settings/ssh-keys")}
type="button"
>
<KeyRoundIcon className="size-4" /> Add SSH Key
</Button>
)}
</div>
<div className="space-y-4">
<FormField

View File

@@ -4,9 +4,9 @@ import { api } from "@/utils/api";
import { GitBranch, LockIcon } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
import { ComposeFileEditor } from "../compose-file-editor";
import { SaveGitProviderCompose } from "./save-git-provider-compose";
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
type TabState = "github" | "git" | "raw";
interface Props {

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -7,12 +8,11 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { Dices } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { Input } from "@/components/ui/input";
interface Props {
composeId: string;

View File

@@ -25,7 +25,6 @@ export const RedbuildCompose = ({ composeId }: Props) => {
},
{ enabled: !!composeId },
);
const { mutateAsync: markRunning } = api.compose.update.useMutation();
const { mutateAsync } = api.compose.redeploy.useMutation();
const utils = api.useUtils();
return (
@@ -53,23 +52,14 @@ export const RedbuildCompose = ({ composeId }: Props) => {
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await markRunning({
toast.success("Redeploying Compose....");
await mutateAsync({
composeId,
composeStatus: "running",
})
.then(async () => {
await mutateAsync({
await utils.compose.one.invalidate({
composeId,
})
.then(async () => {
await utils.compose.one.invalidate({
composeId,
});
toast.success("Compose rebuild succesfully");
})
.catch(() => {
toast.error("Error to rebuild the compose");
});
});
})
.catch(() => {
toast.error("Error to rebuild the compose");

View File

@@ -1,3 +1,4 @@
import { Badge } from "@/components/ui/badge";
import {
Card,
CardContent,
@@ -5,11 +6,10 @@ import {
CardHeader,
CardTitle,
} 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 React from "react";
import { ComposeActions } from "./actions";
import { ShowProviderFormCompose } from "./generic/show";
interface Props {
composeId: string;
}

View File

@@ -25,7 +25,6 @@ export const StopCompose = ({ composeId }: Props) => {
},
{ enabled: !!composeId },
);
const { mutateAsync: markRunning } = api.compose.update.useMutation();
const { mutateAsync, isLoading } = api.compose.stop.useMutation();
const utils = api.useUtils();
return (
@@ -47,26 +46,17 @@ export const StopCompose = ({ composeId }: Props) => {
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await markRunning({
await mutateAsync({
composeId,
composeStatus: "running",
})
.then(async () => {
await mutateAsync({
await utils.compose.one.invalidate({
composeId,
})
.then(async () => {
await utils.compose.one.invalidate({
composeId,
});
toast.success("Compose rebuild succesfully");
})
.catch(() => {
toast.error("Error to rebuild the compose");
});
});
toast.success("Compose stopped succesfully");
})
.catch(() => {
toast.error("Error to rebuild the compose");
toast.error("Error to stop the compose");
});
}}
>

View File

@@ -1,4 +1,3 @@
import dynamic from "next/dynamic";
import {
Card,
CardContent,
@@ -6,6 +5,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
@@ -16,8 +16,8 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
import { Label } from "@/components/ui/label";
export const DockerLogs = dynamic(
() =>
import("@/components/dashboard/docker/logs/docker-logs-id").then(
@@ -30,12 +30,14 @@ export const DockerLogs = dynamic(
interface Props {
appName: string;
appType: "stack" | "docker-compose";
}
export const ShowDockerLogsCompose = ({ appName }: Props) => {
export const ShowDockerLogsCompose = ({ appName, appType }: Props) => {
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
appType,
},
{
enabled: !!appName,

View File

@@ -5,8 +5,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { useEffect, useState } from "react";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
@@ -16,7 +15,8 @@ import {
SelectTrigger,
SelectValue,
} 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";
interface Props {
@@ -31,6 +31,7 @@ export const ShowMonitoringCompose = ({
const { data } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName: appName,
appType,
},
{
enabled: !!appName,

View File

@@ -1,3 +1,5 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
@@ -7,7 +9,6 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
@@ -16,16 +17,15 @@ import {
FormLabel,
FormMessage,
} 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 { SquarePen } from "lucide-react";
import { Textarea } from "@/components/ui/textarea";
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({
name: z.string().min(1, {

View File

@@ -1,4 +1,11 @@
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Dialog,
DialogContent,
@@ -11,36 +18,29 @@ import {
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormDescription,
FormMessage,
} from "@/components/ui/form";
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 {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { z } from "zod";
import { cn } from "@/lib/utils";
import { ScrollArea } from "@/components/ui/scroll-area";
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({
destinationId: z.string().min(1, "Destination required"),

View File

@@ -1,4 +1,11 @@
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Dialog,
DialogContent,
@@ -18,28 +25,21 @@ import {
FormMessage,
} from "@/components/ui/form";
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 {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Switch } from "@/components/ui/switch";
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({
destinationId: z.string().min(1, "Destination required"),

View File

@@ -1,7 +1,7 @@
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import React, { useEffect } from "react";
import { Terminal } from "@xterm/xterm";
import React, { useEffect } from "react";
import { FitAddon } from "xterm-addon-fit";
import "@xterm/xterm/css/xterm.css";
@@ -23,8 +23,11 @@ export const DockerLogsId: React.FC<Props> = ({ id, containerId }) => {
cursorBlink: true,
cols: 80,
rows: 30,
lineHeight: 1.4,
lineHeight: 1.25,
fontWeight: 400,
fontSize: 14,
fontFamily:
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
convertEol: true,
theme: {

View File

@@ -1,5 +1,3 @@
import dynamic from "next/dynamic";
import React from "react";
import {
Dialog,
DialogContent,
@@ -9,6 +7,8 @@ import {
DialogTrigger,
} from "@/components/ui/dialog";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import dynamic from "next/dynamic";
import type React from "react";
export const DockerLogsId = dynamic(
() =>
import("@/components/dashboard/docker/logs/docker-logs-id").then(

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import type { ColumnDef } from "@tanstack/react-table";
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {

View File

@@ -1,4 +1,3 @@
import * as React from "react";
import {
type ColumnFiltersState,
type SortingState,
@@ -11,6 +10,7 @@ import {
useReactTable,
} from "@tanstack/react-table";
import { ChevronDown } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {
@@ -28,7 +28,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { api, type RouterOutputs } from "@/utils/api";
import { type RouterOutputs, api } from "@/utils/api";
import { columns } from "./colums";
export type Container = NonNullable<
RouterOutputs["docker"]["getContainers"]
@@ -161,10 +161,6 @@ export const ShowContainers = () => {
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="space-x-2 flex flex-wrap">
<Button
variant="outline"

View File

@@ -6,8 +6,8 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import dynamic from "next/dynamic";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import dynamic from "next/dynamic";
const Terminal = dynamic(
() => import("./docker-terminal").then((e) => e.DockerTerminal),

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