Compare commits

..

455 Commits

Author SHA1 Message Date
Mauricio Siu 973b54408e Merge pull request #1027 from Dokploy/canary
v0.16.1
2024-12-29 18:39:17 -06:00
Mauricio Siu 638fbe17a6 refactor: update paths 2024-12-29 18:23:07 -06:00
Mauricio Siu 75e0d0ba78 refactor: add is cloud validation 2024-12-29 18:14:32 -06:00
Mauricio Siu 0f1b911236 chore: bump version 2024-12-29 18:08:20 -06:00
Mauricio Siu da148a6c50 Merge pull request #959 from szwabodev/feat/localServerTerminal
feat: local server terminal access
2024-12-29 18:06:32 -06:00
Mauricio Siu c168648cce Merge pull request #1026 from 190km/fix/missing-norwegian-select
fix: missing norwegian lang in select
2024-12-29 17:40:16 -06:00
190km efd3ad4102 fix: missing norwegian lang in select 2024-12-30 00:30:57 +01:00
Mauricio Siu a9577395eb Merge pull request #1025 from Dokploy/998-add-unifi-applicaton
feat: add unifi template
2024-12-29 17:19:33 -06:00
Mauricio Siu 1d02d4308f feat: add unifi template 2024-12-29 17:19:17 -06:00
Mauricio Siu e93e15a9c8 Merge pull request #995 from 190km/refactor-i18n
feat: i18n displays real lang name
2024-12-29 16:14:03 -06:00
Mauricio Siu 511a9beaa5 Merge pull request #1024 from Dokploy/fix/add-fallback-getIp
refactor: add fallback ipv4
2024-12-29 16:13:48 -06:00
Mauricio Siu ac8c180ba6 refactor: add fallback ipv4 2024-12-29 16:13:06 -06:00
Mauricio Siu 9a2898ba4f Merge pull request #1023 from Dokploy/1008-s3-provider-dropdown-default-value-reset
fix: add provider value in edit destination
2024-12-29 15:56:50 -06:00
Mauricio Siu 2c258c84eb fix: add provider value in edit destination 2024-12-29 15:56:30 -06:00
Mauricio Siu 36511f34f8 Merge branch 'canary' into refactor-i18n 2024-12-29 15:51:17 -06:00
Mauricio Siu fee5bbc535 Merge pull request #1022 from Dokploy/fix/cron-jobs
fix: add try catch inside of cron jobs
2024-12-29 15:49:28 -06:00
Mauricio Siu 84ad9a5bb3 refactor: revert notifications 2024-12-29 15:45:54 -06:00
Mauricio Siu 69576988eb Merge pull request #1021 from Dokploy/fix/memory-reservation
Fix/memory reservation
2024-12-29 15:36:14 -06:00
Mauricio Siu d65d050494 fix: add try catch inside of cron jobs 2024-12-29 15:35:15 -06:00
Mauricio Siu 46a5adf793 refactor: remove muted text 2024-12-29 14:17:15 -06:00
Mauricio Siu 5558ee3248 fix: add tooltip and placeholder values 2024-12-29 14:10:41 -06:00
Mauricio Siu 5d8ebd027e fix: parse correctly numbers 2024-12-29 13:53:38 -06:00
Mauricio Siu 9aa49ce9be Merge pull request #1019 from jocubeit/canary
Spelling and grammar corrections
2024-12-29 13:52:31 -06:00
Mauricio Siu 7bcfc17fe9 Merge pull request #1020 from champ3oy/patch-1
convert email to lower case when loging in
2024-12-29 13:34:27 -06:00
cirlorm 5d2d4104f0 convert email to lower case when loging in
I had an issue signing in to my dashboard only to find out it was because the first letter of my email was in caps.
2024-12-29 17:46:44 +00:00
Dom 2af8d6f565 Spelling and grammar corrections 2024-12-29 22:34:57 +10:00
Mauricio Siu ba45b27608 Merge pull request #1010 from joaotonaco/canary
feat: prevent layout shifting from scrollbar
2024-12-28 19:31:36 -06:00
Mauricio Siu 5aa1c6efb7 Merge pull request #1011 from joaotonaco/fix/inconsistent-service-card-height
fix: inconsistent service card height
2024-12-28 19:30:42 -06:00
Mauricio Siu e9dad8f9f8 Merge pull request #1016 from 190km/style/swarm
feat: swarm overview style
2024-12-28 19:30:13 -06:00
Mauricio Siu 0c2a382541 Update apps/dokploy/components/dashboard/settings/servers/show-swarm-overview-modal.tsx 2024-12-28 19:29:51 -06:00
190km 81408985d4 feat: swarm overview style 2024-12-29 01:07:47 +01:00
João Gabriel d45b558251 fix: inconsistent service card height 2024-12-27 11:08:55 -03:00
João Gabriel a30b1a0cb8 refactor: prevent layout shifting from scrollbar 2024-12-27 10:51:31 -03:00
João Gabriel c946e3e01b refactor: prevent layout shifting from scrollbar 2024-12-27 10:50:36 -03:00
Mauricio Siu eb7bae2ef5 Merge pull request #1006 from Dokploy/canary
v0.16.0
2024-12-26 22:52:02 -06:00
Mauricio Siu 4533b193a4 chore: bump version 2024-12-26 22:32:44 -06:00
Mauricio Siu c22f744e6c Merge pull request #1000 from faytranevozter/feat/simplify-node-logs
Feat: simplify node service log & add badge color on list service/container
2024-12-26 22:23:43 -06:00
Mauricio Siu d3663eba6b Merge pull request #1005 from Dokploy/fix/swarm-overview
fix: remove unused parsed
2024-12-26 22:18:52 -06:00
Mauricio Siu f9e4a71144 fix: remove unused parsed 2024-12-26 22:14:18 -06:00
faytranevozter 985b8bc2e0 refactor: extract badgeStateColor function for reuse across log components 2024-12-27 08:48:36 +07:00
Mauricio Siu 87ef889114 Merge pull request #999 from 190km/logs-typo
fix: typo Logssss > Logs
2024-12-26 13:31:57 -06:00
Mauricio Siu f3494922be Merge pull request #997 from Barsnes/feat/norwegian-lang
feat(i18n): add Norwegian language support
2024-12-26 13:31:21 -06:00
faytranevozter 27252cf58d feat: add badge component to display container state with color coding 2024-12-27 01:44:46 +07:00
faytranevozter f69fb7684b fix: normalize state string to lowercase and update filter quotes in docker service functions 2024-12-27 01:44:10 +07:00
faytranevozter 20a7995d73 fix: update docker logs command to conditionally include raw flag for swarm 2024-12-27 01:43:52 +07:00
faytranevozter 6df66c3871 fix: correct typo in logs title 2024-12-27 01:34:20 +07:00
usopp cbfdda1928 fix: typo Logssss > Logs 2024-12-26 17:20:18 +01:00
Tobias Barsnes 766279f265 fix: add norwegian common.json file 2024-12-26 13:30:29 +01:00
Tobias Barsnes f6e4ae700a feat(i18n): add Norwegian language support 2024-12-26 13:26:16 +01:00
Mauricio Siu 8c8ffe04a7 Merge pull request #926 from thewilloftheshadow/feat/mongo-replica-sets
feat: mongo replica sets
2024-12-25 23:37:29 -06:00
190km f242f5c65e chore: lint 2024-12-26 01:29:18 +01:00
190km 46348f43f6 chore: lint 2024-12-26 01:26:53 +01:00
190km c3476a1fdf refactor: refactored i18n to display real lang name 2024-12-26 00:04:50 +01:00
Mauricio Siu 379ea2ac65 Merge pull request #994 from jmsx/canary
feat(i18n): add Spanish language support
2024-12-25 16:34:24 -06:00
Jose Manuel Gonzalez a172abaee4 feat(i18n): add Spanish language support 2024-12-25 22:44:31 +01:00
Mauricio Siu a325b293b6 Merge pull request #990 from Dokploy/986-error-to-save-the-deployment
fix: omit macos files and create folders every time
2024-12-25 13:48:48 -06:00
Mauricio Siu 364c2e192e fix: adjust types 2024-12-25 13:45:24 -06:00
Mauricio Siu d4a3c5cff9 fix: omit macos files and create folders every time 2024-12-25 04:51:49 -06:00
Mauricio Siu 229a9a3a5e Merge pull request #989 from Dokploy/refactor/add-no-trunc
Refactor/add no trunc
2024-12-25 03:54:51 -06:00
Mauricio Siu 5c1993a647 refactor: exclude dokploy services 2024-12-25 03:54:25 -06:00
Mauricio Siu 64a449a09d refactor: add swarm improvements 2024-12-25 03:45:21 -06:00
Mauricio Siu e65e6d225f refactor: add no trunc 2024-12-25 03:35:21 -06:00
Mauricio Siu b4fcdc433e feat: add migration 2024-12-25 03:27:46 -06:00
Mauricio Siu 5ce6172187 Merge branch 'canary' into feat/mongo-replica-sets 2024-12-25 03:27:00 -06:00
Mauricio Siu 0affeea5dd remove 2024-12-25 03:26:06 -06:00
Mauricio Siu 8f76d520c8 feat: add replica sets migration 2024-12-25 03:24:18 -06:00
Mauricio Siu 566d9e0bee Merge pull request #931 from faytranevozter/feature/node-cluster-logs
feat: add support for viewing docker logs in swarm mode
2024-12-25 02:52:49 -06:00
Mauricio Siu da858e215d refactor: add toggle for normal containers & stack 2024-12-25 02:51:44 -06:00
Mauricio Siu f672c429c4 Merge branch 'canary' into feature/node-cluster-logs 2024-12-25 02:15:14 -06:00
Mauricio Siu ce34fe3cd8 Merge pull request #985 from Barsnes/chore/autocomplete-off
fix: turn off `autoComplete` for some inputs
2024-12-25 00:48:36 -06:00
Tobias Barsnes d8dbdb2b9e fix: turn off autoComplete for some inputs 2024-12-25 03:23:02 +01:00
Mauricio Siu 4065ad4428 Update apps/dokploy/components/dashboard/project/add-database.tsx
Co-authored-by: Dominik Koch <dominik@koch-bautechnik.de>
2024-12-24 15:34:09 -06:00
Mauricio Siu e035062a10 Merge pull request #971 from drudge/cert-enhancements
fix(certificates): ensure certificates are accessible by traefik
2024-12-24 15:33:26 -06:00
Mauricio Siu 36a1daae4b Merge pull request #984 from Dokploy/feat/add-hide-extra-logs
feat: add hide extra logs
2024-12-24 15:13:18 -06:00
Mauricio Siu 830a254837 Merge pull request #979 from drudge/compose-builder-error
fix: show error message when compose builder throws
2024-12-24 15:10:19 -06:00
Mauricio Siu b8580d69d6 feat: add hide extra logs 2024-12-24 15:09:57 -06:00
Mauricio Siu e3f1518b0d Merge pull request #977 from drudge/ports-ui
feat(traefik/ports): improved UI
2024-12-24 14:47:58 -06:00
Mauricio Siu ce19a42aee refactor: use react hook form 2024-12-24 14:47:42 -06:00
Mauricio Siu 28a2ab9aa5 Merge pull request #974 from drudge/preview-deployments
feat(preview-deployments): new UI
2024-12-24 14:31:37 -06:00
Mauricio Siu 0535d780b1 refactor: add missing running state 2024-12-24 14:31:05 -06:00
Mauricio Siu e1dd666e24 Merge pull request #972 from drudge/traefik-port
fix(setup): use traefik port/version from env
2024-12-24 14:28:10 -06:00
Mauricio Siu ce71fa4f4d Merge pull request #962 from joaotonaco/feat/discord-emoji-toggle
feat(notifications): discord decoration toggle
2024-12-24 14:13:09 -06:00
Nicholas Penree 364d04f238 fix: show error message when compose builder throws
(cherry picked from commit 3633e3d66ab73a89382d0a2f079af63f57289419)
2024-12-23 22:13:49 -05:00
Nicholas Penree 0db98c0b92 feat(traefik/ports): improved UI 2024-12-23 21:39:16 -05:00
Nicholas Penree 8410d94283 feat(preview-deployments): new UI 2024-12-23 16:55:13 -05:00
Nicholas Penree 56cfd35e7d feat(certificates): clarify you are selecting the cert provider 2024-12-23 12:37:53 -05:00
Nicholas Penree e7beb5c75b fix(setup): use traefik port/version from env 2024-12-23 10:32:31 -05:00
Nicholas Penree 9c5a61e42f fix(certificates): show proper placeholder for private key format 2024-12-23 09:17:11 -05:00
Nicholas Penree 0ee5a6f13e fix(certificates): ensure certificates are accessible by traefik 2024-12-23 09:16:10 -05:00
João Gabriel 1d35d218ca Merge branch 'canary' into feat/discord-emoji-toggle 2024-12-23 10:08:56 -03:00
Mauricio Siu 96cdffb5b9 Merge pull request #886 from DJKnaeckebrot/feature/delete-docker-volumes
feat: option to delete docker volumes on service deletion
2024-12-23 02:38:28 -06:00
Mauricio Siu 353effd720 refactor: delete log 2024-12-23 02:37:04 -06:00
Mauricio Siu 8bfe1632fa refactor: remove delete volumes 2024-12-23 02:32:43 -06:00
Mauricio Siu ed543e5397 refactor: lint 2024-12-23 02:28:40 -06:00
djknaeckebrot c6892ba188 format: fix formatting 2024-12-23 08:39:50 +01:00
djknaeckebrot fa710d4855 format: fix formatting 2024-12-23 08:39:11 +01:00
djknaeckebrot 375decebb2 chore: remove application delete volume 2024-12-23 08:36:18 +01:00
djknaeckebrot be2e70a17e chore: only make this apply to compose files 2024-12-23 08:27:45 +01:00
djknaeckebrot 3b4214e040 Merge remote-tracking branch 'origin/feature/delete-docker-volumes' into feature/delete-docker-volumes 2024-12-23 08:13:57 +01:00
djknaeckebrot 43a493bb5a Merge branch 'canary' into feature/delete-docker-volumes 2024-12-23 08:13:12 +01:00
Mauricio Siu 455cae6b8c Merge pull request #938 from 190km/preview-deployment-view
style(preview-deployment): better preview deployment card
2024-12-23 00:40:51 -06:00
Mauricio Siu 869843d9ac Merge pull request #921 from DJKnaeckebrot/feature/swarm-overview
feat: add swarm overview page
2024-12-23 00:09:09 -06:00
Mauricio Siu d2b662f547 Merge pull request #904 from acgonzales/fix/api-appname
fix: fixed/improved handling of app names in api
2024-12-23 00:08:40 -06:00
Mauricio Siu 31336152ce Merge pull request #968 from drudge/fix-esc-vi
fix: prevent Esc key from closing terminal modals
2024-12-23 00:07:47 -06:00
Mauricio Siu 6afd443257 feat: add swarm overview for servers 2024-12-23 00:03:30 -06:00
Mauricio Siu 1d023ac9f3 Merge pull request #965 from drudge/fancy-ansi
feat(logs): use fancy-ansi for ansi colors
2024-12-22 23:09:02 -06:00
Devin AI 49616e53ea fix: prevent Esc key from closing terminal modals
Co-Authored-By: Nicholas Penree <nick@penree.com>
(cherry picked from commit ee469d5ac3f72cc8e010728cc7c6273c711875b0)
2024-12-22 23:59:57 -05:00
Mauricio Siu a32e934969 Merge pull request #967 from 190km/white-theme-notifications
style(notifications): fixed white style
2024-12-22 22:59:49 -06:00
190km eb495b7b99 style: fixed white style 2024-12-23 03:15:11 +01:00
190km 65ddc22010 refactor: removed useless export 2024-12-23 02:41:08 +01:00
190km 7a5b9e3b76 refactor: deleted separate component, and add it to show preview build 2024-12-23 02:34:50 +01:00
Nicholas Penree 5a302d3c47 feat(logs): use fancy-ansi for ansi colors 2024-12-22 15:20:54 -05:00
DJKnaeckebrot 5c5066bc72 chore: remove server from swarms page 2024-12-22 20:07:48 +01:00
UndefinedPony 228d12a61c refactor: code cleanup 2024-12-22 19:01:02 +01:00
UndefinedPony fd0a9b8b58 feat: add local server config, add local server support in terminal modal, add fixed terminal container height 2024-12-22 18:46:02 +01:00
UndefinedPony 1ea7d2e1bf feat: enable server translations for servers page 2024-12-22 18:44:38 +01:00
UndefinedPony 7e08c8881e refactor: adapt terminal component to pass local server data and add initialized check 2024-12-22 16:37:26 +01:00
UndefinedPony e68d867d31 refactor: adapt terminal wss to allow local server connection, add status logs 2024-12-22 16:35:28 +01:00
UndefinedPony a53929a787 feat: add enter terminal option to server dropdown 2024-12-22 16:32:20 +01:00
UndefinedPony ec8eaf6249 feat: add util to configure and get private ssh key for local ssh connection 2024-12-22 16:31:52 +01:00
UndefinedPony 0d58935ad1 refactor: translations for common terminal stuff 2024-12-22 16:30:53 +01:00
Mauricio Siu f7c8324c4b Merge pull request #963 from Dokploy/540-need-more-control-over-docker-compose-command-in-deployments
540 need more control over docker compose command in deployments
2024-12-22 01:55:49 -06:00
Mauricio Siu c0e9670daf refactor: delete log 2024-12-22 01:53:30 -06:00
Mauricio Siu a710728e77 feat: add support for custom command 2024-12-22 01:51:05 -06:00
João Gabriel 80bd80b786 refactor: make decoration prop optional at discord tester schema 2024-12-22 01:08:34 -03:00
João Gabriel 6e262cde0d fix: update discord notification decoration 2024-12-22 01:07:29 -03:00
João Gabriel 21b2cce7a0 fix: send decoration prop to notification tester 2024-12-22 01:06:47 -03:00
João Gabriel 870e074825 feat: implement decoration to embeds 2024-12-22 00:44:25 -03:00
João Gabriel 055b59e6fa feat: discord decoration switch 2024-12-22 00:42:00 -03:00
João Gabriel 8c06296503 feat: add decoration column to discord notification 2024-12-22 00:39:18 -03:00
190km 29ce8908ee refactor: remove all the props except id, serverid, isloading, and delete function 2024-12-21 23:11:55 +01:00
Mauricio Siu 839e1c0f9f Merge pull request #961 from Dokploy/canary
v0.15.1
2024-12-21 15:42:19 -06:00
Mauricio Siu 54dd531a26 Merge branch 'main' into canary 2024-12-21 15:36:20 -06:00
Mauricio Siu 7ebf5ad0f9 chore: bump version 2024-12-21 15:28:27 -06:00
Mauricio Siu b85163d935 refactor: add conditional value 2024-12-21 15:10:00 -06:00
Mauricio Siu a953e59327 Merge pull request #960 from Dokploy/fix/add-latest-cases
refactor: update digest
2024-12-21 15:00:51 -06:00
Mauricio Siu b2661e4533 refactor: update 2024-12-21 14:58:58 -06:00
Mauricio Siu 883459624e refactor: update digest 2024-12-21 14:54:14 -06:00
Mauricio Siu 6e2b2d564b refactor: add missing additional ports 2024-12-21 14:11:06 -06:00
Mauricio Siu 065963857c Merge pull request #958 from Dokploy/fix/update-fetch
Fix/update fetch
2024-12-21 13:50:13 -06:00
Mauricio Siu a0c9df4bd4 refactor: add 7 minutes interval 2024-12-21 13:35:17 -06:00
Mauricio Siu 68c8c70260 refactor: iterate pages 2024-12-21 13:34:50 -06:00
Mauricio Siu a926f28d30 Merge pull request #930 from lost-end-found/additional-ports-management
feat(ports): implement additional ports management
2024-12-21 13:30:32 -06:00
Mauricio Siu 59c0636fb0 refactor: add is cloud to fn 2024-12-21 13:19:30 -06:00
Mauricio Siu ae159c5678 refactor: add tag to web server update 2024-12-21 13:13:38 -06:00
Mauricio Siu 0abf62dd52 refactor: prevent update appName in services 2024-12-21 13:10:34 -06:00
UndefinedPony e2b155280a feat: add enter terminal translation 2024-12-21 20:08:08 +01:00
Mauricio Siu e42e9bec17 refactor: use schema inline 2024-12-21 12:48:32 -06:00
Mauricio Siu 978324e2bf Merge pull request #957 from drudge/check-for-updates
New Updates UI
2024-12-21 12:47:24 -06:00
Nicholas Penree 8f05f06259 Merge remote-tracking branch 'dokploy/canary' into check-for-updates 2024-12-21 13:47:01 -05:00
Mauricio Siu 392be2cfa2 Merge pull request #951 from szwabodev/checkUpdatesTweaks
feat: automatic check for updates
2024-12-21 12:45:39 -06:00
Mauricio Siu 18e89df9a5 Update apps/dokploy/components/layouts/navbar.tsx 2024-12-21 12:45:14 -06:00
UndefinedPony 4d2a9f8aa7 refactor: use dynamic tag for comparing latest tag digest 2024-12-21 13:35:39 -05:00
Nicholas Penree d08530d451 feat(updates): clean up light mode 2024-12-21 13:02:58 -05:00
UndefinedPony 6c9b12cee9 refactor: use dynamic tag for comparing latest tag digest 2024-12-21 18:33:22 +01:00
Nicholas Penree a8ff6c7b3f feat(updates): new update UI 2024-12-21 11:24:19 -05:00
UndefinedPony 8699e024ee refactor: add try catch, add default update data 2024-12-21 10:05:31 +01:00
Mauricio Siu 73782ffd26 Merge pull request #954 from Dokploy/915-daily-docker-cleanup-seems-to-be-doing-nothing
fix: add missing notifications in cron jobs
2024-12-21 02:51:52 -06:00
Mauricio Siu 7a8bb8f71d fix: add missing notifications in cron jobs 2024-12-21 02:45:58 -06:00
UndefinedPony 18eae9f7d7 refactor: use service image sha instead of image itself for checking updates 2024-12-21 09:04:25 +01:00
Mauricio Siu 1aae523a0b refactor: add missing verifyToken 2024-12-21 01:53:39 -06:00
UndefinedPony f40e802331 fix: pull latest release in case of no image when checking update 2024-12-21 08:47:21 +01:00
Mauricio Siu d979aa17c2 Merge pull request #948 from 190km/notifications-style
style(notifications): better notification item style
2024-12-20 23:34:57 -06:00
Mauricio Siu e2d20fb0e3 Merge pull request #929 from drudge/certificate-details
feat(certs): show expiration and chain details
2024-12-20 23:23:33 -06:00
Mauricio Siu 62f59c1f9a Merge pull request #941 from mezotv/bump-plausible
feat(plausible): bump to 2.1.4
2024-12-20 23:17:22 -06:00
Mauricio Siu 93e1071057 Merge pull request #949 from 190km/fix-edit-password
fix(settings/profile): edit profile password fixed
2024-12-20 23:17:07 -06:00
Mauricio Siu 788771c5eb refactor: add password in validation 2024-12-20 23:16:38 -06:00
UndefinedPony ab9aa56c48 refactor: disable automatic updates for cloud version 2024-12-20 18:57:28 +01:00
UndefinedPony 4565b3d7a2 refactor: add latestVersion information to update data 2024-12-20 18:26:54 +01:00
UndefinedPony c8514e3a1b refactor: remove unused async 2024-12-20 17:32:10 +01:00
UndefinedPony a06dd17aa1 feat(navbar): add automatic update checking interval, add update available button 2024-12-20 17:30:14 +01:00
UndefinedPony 256534570b refactor: add image tag helper, refactor update check logic, remove try/catch 2024-12-20 17:29:01 +01:00
UndefinedPony 2804748118 refactor: rename action, move pull to updateServer 2024-12-20 17:27:51 +01:00
UndefinedPony e6bc40e7fe refactor: adapt to navbar version, move confirm action, add reload info 2024-12-20 17:27:14 +01:00
UndefinedPony 196603126b refactor: move check updates function, use new api 2024-12-20 17:26:31 +01:00
UndefinedPony a5cd8f18cd feat: show auto check update toggle 2024-12-20 17:23:02 +01:00
UndefinedPony b842887bc3 feat: add toggle for auto updates checking 2024-12-20 16:43:05 +01:00
UndefinedPony dd64b06340 style: format with biome 2024-12-20 14:09:05 +01:00
UndefinedPony d9a1976cc0 fix: check updates message fixes 2024-12-20 14:01:55 +01:00
190km fdfa927532 feat(settings/profile): reset password form after validating password change 2024-12-20 01:00:16 +01:00
190km bf2551b0f6 fix(settings/profile): fixed password changing 2024-12-20 00:54:32 +01:00
usopp ed8be62ff3 chore: lint 2024-12-20 00:24:44 +01:00
usopp 77336a21f9 chore: lint 2024-12-20 00:21:51 +01:00
usopp e05d01788f style(notifications): better notification item style 2024-12-20 00:11:48 +01:00
Dominik Koch 651e81ce6d feat(plausible): bump to 2.1.4 2024-12-19 11:22:58 +01:00
Mauricio Siu fac29b70a5 Merge pull request #937 from drudge/fix-custom-registry
fix(docker): fix for custom registry login
2024-12-19 02:14:34 -06:00
Mauricio Siu 95eaab43df Merge pull request #936 from drudge/fix-light-term
fix(term): fix light mode foreground color
2024-12-19 02:14:26 -06:00
Mauricio Siu abdef13b93 refactor: set current color 2024-12-19 02:14:06 -06:00
Mauricio Siu 65f397e1b1 Merge pull request #939 from 190km/2fa-typo-login
fix(2fa-login): typo - Setup -> Login
2024-12-19 02:08:26 -06:00
Mauricio Siu 1ae96297e8 refactor: update lint 2024-12-19 02:07:08 -06:00
Mauricio Siu c51b502116 refactor: add path join to prevent concatenate double slash and update the getImageName 2024-12-19 02:05:30 -06:00
Mauricio Siu 5a42b78098 Merge pull request #940 from drudge/wrap-secrets
fix(preview-deployments): wrap long envs
2024-12-19 01:19:01 -06:00
Nicholas Penree b39c0ef915 fix(preview-deployments): wrap long envs 2024-12-18 23:00:50 -05:00
Nicholas Penree 844d582147 fix(docker): fix for custom registry login 2024-12-18 21:58:22 -05:00
190km 0b51088489 fix: typo - Setup -> Login 2024-12-19 02:10:07 +01:00
usopp 9d1cf3736b chore: lint 2024-12-19 01:58:18 +01:00
usopp 3a95474662 chore: lint 2024-12-19 01:44:20 +01:00
190km 3858205e52 style: better preview deployment card 2024-12-18 22:36:57 +01:00
Nicholas Penree 1dece58cff fix(term): fix light mode foreground color
closes #907
2024-12-18 13:56:09 -05:00
Shadow 06b8c82484 feat: add a toggle for replica sets to be used or not 2024-12-18 11:50:30 -06:00
DJKnaeckebrot 8ea453f444 feat: add application handling 2024-12-18 13:01:09 +01:00
djknaeckebrot 63c0912849 xMerge branch 'canary' into feature/delete-docker-volumes 2024-12-18 12:10:04 +01:00
faytranevozter 6211a19805 feat: add support for viewing docker logs in swarm mode 2024-12-18 16:40:33 +07:00
Larry Ioannidis d22330f983 feat(ports): implement additional ports management 2024-12-18 09:39:20 +00:00
djknaeckebrot 8642d8235e chore: add seperator and make tittles big 2024-12-18 09:14:10 +01:00
djknaeckebrot b52f57cb0d chore: remove imports 2024-12-18 08:51:49 +01:00
djknaeckebrot d4d74d3831 refactor: remove not needed import, move to better folder structure 2024-12-18 08:49:02 +01:00
djknaeckebrot 9d497142db feat: add latest cards 2024-12-18 08:43:07 +01:00
Mauricio Siu 852895c382 Merge pull request #912 from drudge/new-ansi-logs
feat(logs): support ansi codes
2024-12-17 23:55:12 -06:00
Mauricio Siu 20d5913820 Merge pull request #910 from drudge/canary
feat(cluster): use code editor for node config
2024-12-17 23:52:25 -06:00
Mauricio Siu f1b4a73158 Merge pull request #928 from drudge/discord-notif
feat(discord): remove dots
2024-12-17 23:51:01 -06:00
Mauricio Siu 3830f6c4ee Merge pull request #925 from mohabgabber/canary
Added onedev docker compose
2024-12-17 23:50:33 -06:00
Mauricio Siu 5c8eda2405 Update apps/dokploy/templates/onedev/docker-compose.yml 2024-12-17 23:47:48 -06:00
Mauricio Siu 6bf85bcfa3 Merge branch 'canary' into new-ansi-logs 2024-12-17 23:43:18 -06:00
Mauricio Siu bc03e718bf Merge pull request #911 from 190km/mutlti-select-fiter-logs
feat(logs): multi select fiter logs & hide/show timestamp
2024-12-17 23:42:50 -06:00
Nicholas Penree a941efb1ff feat(certs): show expiration and chain details 2024-12-17 23:17:29 -05:00
Nicholas Penree fe2de6b899 feat(discord): remove dots 2024-12-17 22:03:58 -05:00
djknaeckebrot 752c9f2818 style: remove bg and border 2024-12-17 21:35:32 +01:00
djknaeckebrot 577b126e66 feat: make bg transparent 2024-12-17 21:19:38 +01:00
djknaeckebrot be237ae4cf chore: comment out refresh for now 2024-12-17 21:07:47 +01:00
djknaeckebrot 3080926a50 feat: add new items 2024-12-17 21:07:30 +01:00
djknaeckebrot e3ee89104b chore: remove tables and add new cards 2024-12-17 20:48:56 +01:00
djknaeckebrot f98f18b331 feat: add monitoring card 2024-12-17 20:48:35 +01:00
Shadow 8505236263 feat: mongo replica sets 2024-12-17 12:59:18 -06:00
usopp b3313cf975 style: better white style 2024-12-17 19:16:40 +01:00
Mohab Gabber 4e31d8ac02 Added onedev to templates.ts and onedev's icon 2024-12-17 18:51:38 +02:00
Mohab Gabber 536507377d Added onedev docker compose 2024-12-17 17:23:26 +02:00
djknaeckebrot 763219e859 refactor: streamline imports and improve code formatting in ShowSwarmNodes component 2024-12-17 12:12:48 +01:00
djknaeckebrot 3fc5bfc5c5 feat: implement fetching of Docker service applications and their details 2024-12-17 12:11:43 +01:00
djknaeckebrot 813da8f811 refactor: clean up code formatting and improve readability in swarm dashboard 2024-12-17 12:06:08 +01:00
djknaeckebrot 5716954665 feat: add components for displaying swarm node details and applications 2024-12-17 12:05:39 +01:00
djknaeckebrot 04d3eb9ec0 feat: add swarm tab and dashboard page for managing Docker swarm 2024-12-17 12:05:02 +01:00
djknaeckebrot b592a025e4 feat: add swarm router and related Docker service functions 2024-12-17 12:04:49 +01:00
Nicholas Penree 6db9c99080 feat(logs): add number of lines filter 2024-12-16 20:12:05 -05:00
Nicholas Penree 7e8953ff44 chore: lint 2024-12-16 17:18:11 -05:00
Nicholas Penree 81c85ce155 fix: don't trigger if already selected 2024-12-16 17:12:18 -05:00
Nicholas Penree bd16e03602 chore: lint 2024-12-16 17:07:31 -05:00
190km 87a5ce2053 fix: timestamp width 2024-12-16 22:55:36 +01:00
Nicholas Penree ca4820940e feat(logs): filter improvements 2024-12-16 16:55:13 -05:00
190km 71fe6de9cb feat(logs): added show/hide timestamp option 2024-12-16 21:27:32 +01:00
Nicholas Penree 9ff4968e61 feat(logs): support ansi codes 2024-12-16 14:59:35 -05:00
190km 2312ae1c12 fix: fixed lint 2024-12-16 19:56:47 +01:00
190km b03011a94f feat(logs): replaced the log type component with the new 2024-12-16 19:50:13 +01:00
190km 7577e40b25 feat(logs): added filter log type component 2024-12-16 19:49:09 +01:00
Nicholas Penree 75e34285ef feat(cluster): use code editor for node config 2024-12-16 11:51:17 -05:00
Aaron Gonzales 8e5b0988cf fix: fixed/improved handling of app names in api 2024-12-16 18:13:07 +08:00
Mauricio Siu 038df9c8a7 Merge pull request #897 from Dokploy/canary
v0.15.0
2024-12-15 21:49:15 -06:00
Mauricio Siu 829aa2a63c Merge pull request #901 from Dokploy/feat/security
Feat: add remote server audit
2024-12-15 21:38:37 -06:00
Mauricio Siu 91e90fc379 Merge pull request #900 from drudge/canary
chore: clean up page titles
2024-12-15 21:38:28 -06:00
Mauricio Siu a1e13ee964 refactor: set audit 2024-12-15 21:37:33 -06:00
Mauricio Siu 341af1bd07 feat: add loader to enhance ux/ui 2024-12-15 21:33:10 -06:00
Nicholas Penree 8a274d10eb chore: clean up page titles 2024-12-15 22:21:10 -05:00
Mauricio Siu 6c586f9606 refactor: add experimental advice 2024-12-15 21:18:23 -06:00
Mauricio Siu dcb1ea37c3 feat: add server audit 2024-12-15 21:16:14 -06:00
Mauricio Siu 58c2ceb355 feat: add security audit 2024-12-15 21:13:37 -06:00
Mauricio Siu beae03b53d Merge pull request #898 from drudge/canary
feat(server): monospace script editor
2024-12-15 20:48:38 -06:00
Nicholas Penree 55ec25f5e8 feat(server): edit copy on script editor 2024-12-15 21:24:27 -05:00
Nicholas Penree 9382acb40c feat(server): monospace script editor 2024-12-15 21:16:39 -05:00
Mauricio Siu c0acdc5df1 refactor: add audit 2024-12-15 19:55:45 -06:00
Mauricio Siu 413536a336 chore: bump version 2024-12-15 19:30:05 -06:00
Mauricio Siu 190f45b3a8 Merge pull request #857 from 190km/new-logs
feat(logs): new railway inspired logs
2024-12-15 19:29:26 -06:00
Mauricio Siu e6c242a064 refactor: set logs no found when the search is empty 2024-12-15 19:24:50 -06:00
Mauricio Siu c2fe1eed01 fix: remote server search add long buffering 2024-12-15 19:18:41 -06:00
Mauricio Siu 676082fc5b Merge branch 'canary' into new-logs 2024-12-15 19:01:26 -06:00
Mauricio Siu b676b1a2de Merge pull request #894 from Dokploy/feat/improve-cloud
refactor: remove unused files
2024-12-15 18:50:46 -06:00
Mauricio Siu 5885712c6a Merge pull request #896 from drudge/copy-delete
feat(delete): add quick copy to resource to clipboard in delete modal
2024-12-15 18:49:51 -06:00
Mauricio Siu afedeede16 feat: add self remove accounts 2024-12-15 18:45:02 -06:00
Nicholas Penree 5f09018199 feat(delete): add quick copy to resource to clipboard in delete modal 2024-12-15 19:17:46 -05:00
Mauricio Siu 9d37876bc4 feat: add reset onboarding 2024-12-15 15:03:25 -06:00
Mauricio Siu 775107ec24 feat: add dokploy cloud modal 2024-12-15 14:54:38 -06:00
DJKnaeckebrot 7725b3ca36 Merge branch 'Dokploy:canary' into feature/delete-docker-volumes 2024-12-15 19:42:34 +01:00
Mauricio Siu 5f297fd984 feat: add react tour 2024-12-15 02:14:43 -06:00
Mauricio Siu 86aba9ce3e refactor: remove cols 2024-12-14 23:12:21 -06:00
Mauricio Siu c6e512bec1 refåctor: remove files 2024-12-14 23:06:54 -06:00
Mauricio Siu fc2b0abdb1 Revert "refactor: remove unsued files"
This reverts commit d20f86ffe1.
2024-12-14 23:05:19 -06:00
Mauricio Siu d20f86ffe1 refactor: remove unsued files 2024-12-14 22:46:01 -06:00
190km 1157e08aa1 fix: log line came out of the div 2024-12-15 04:21:38 +01:00
Mauricio Siu e643255a67 Merge pull request #893 from Dokploy/510-fractional-cpu-reservation-and-limit
refactor: remove calculation and pass resources as the docker api expect
2024-12-14 20:40:16 -06:00
Mauricio Siu 7521bc8297 refactor: remove calculation and pass resources as the docker api expect 2024-12-14 20:38:33 -06:00
Mauricio Siu a63981fa15 Merge pull request #892 from Dokploy/680-redirect-to-the-deployment-logs
feat: redirect to deployments when click on deploy
2024-12-14 20:30:15 -06:00
Mauricio Siu ea0f797d0f feat: redirect to deployments when click on deploy 2024-12-14 20:28:28 -06:00
Mauricio Siu 181a2ca3c9 Merge pull request #891 from Dokploy/769-elasticsearch-new-db-template
769 elasticsearch new db template
2024-12-14 20:18:49 -06:00
Mauricio Siu 3fe057c7f8 refactor: set version 2024-12-14 20:17:38 -06:00
Mauricio Siu 1e834ed1d9 feat: add elastic search 2024-12-14 20:17:20 -06:00
Mauricio Siu 9f84545fc7 Merge pull request #890 from Dokploy/889-search-for-templates-only-takes-into-account-the-template-title-missing-description-matches
refactor: add description on search
2024-12-14 12:38:05 -06:00
Mauricio Siu 690a2e7467 refactor: add description on search 2024-12-14 12:37:50 -06:00
190km 995d9004f3 style: fix text light theme 2024-12-14 18:23:00 +01:00
djknaeckebrot ef89d05077 Merge branch 'canary' into feature/delete-docker-volumes 2024-12-14 11:07:08 +01:00
Mauricio Siu 0a3ab7ceac Merge pull request #868 from DJKnaeckebrot/feat/page-titles
feat: add page titles
2024-12-14 02:28:31 -06:00
Mauricio Siu 2fd4d580d5 Merge pull request #880 from DJKnaeckebrot/feature/penpot-template
feat: add penpot template
2024-12-14 02:27:01 -06:00
Mauricio Siu 33e2fa3ce3 Merge branch 'canary' into feature/penpot-template 2024-12-14 02:26:35 -06:00
Mauricio Siu d320847da4 refactor: remove traefik 2024-12-14 02:25:35 -06:00
Mauricio Siu 9e84bf324e Merge pull request #875 from DJKnaeckebrot/feature/huly-template
feat: add huly template
2024-12-14 01:48:11 -06:00
Mauricio Siu db469e60ad Merge branch 'canary' into feature/huly-template 2024-12-14 01:37:13 -06:00
Mauricio Siu 0f949b3273 Merge pull request #874 from DJKnaeckebrot/feature/langflow-template
feat: add langflow template
2024-12-14 01:36:23 -06:00
Mauricio Siu 166b65c50e refactor: add env variables 2024-12-14 01:34:05 -06:00
Mauricio Siu 274c65cbcd refactor: add unsend 2024-12-14 01:29:39 -06:00
Mauricio Siu b538a632d9 Merge branch 'canary' into feature/langflow-template 2024-12-14 01:28:38 -06:00
Mauricio Siu 765c6442cb Merge pull request #869 from DJKnaeckebrot/feature/unsend-dev-template
feat: add unsend template
2024-12-14 01:24:09 -06:00
Mauricio Siu 115ed7e7bf refactor: remove is loading false 2024-12-14 01:17:13 -06:00
190km 0644842305 style: selects width 2024-12-14 03:00:05 +01:00
190km a9c62b47ef style: selects wididth 2024-12-14 02:58:21 +01:00
190km 138650d561 feat: improved deployment view scroll & style 2024-12-14 01:30:36 +01:00
190km 280be5c9df style: fixed select sizes 2024-12-13 21:13:10 +01:00
190km 7726fa6112 style: make selects responsive 2024-12-13 20:29:26 +01:00
190km c71d12fd06 feat: added info possibilities & debug more debug possibilities 2024-12-13 20:21:00 +01:00
usopp 3df3d187e4 feat: added deployment loader & lines count 2024-12-13 19:41:02 +01:00
djknaeckebrot 8ce9db8dd6 feat: add penpot template 2024-12-13 19:40:49 +01:00
190km 6773458da3 fix: text came out of the parent div 2024-12-13 18:29:18 +01:00
djknaeckebrot 92c2a83d92 feat: add filter to delete volumes is wanted 2024-12-13 16:34:46 +01:00
djknaeckebrot 3decbd5207 feat: add new form validator 2024-12-13 16:34:29 +01:00
djknaeckebrot 8779c67b71 refactor: change import names 2024-12-13 16:34:12 +01:00
djknaeckebrot 4dc7d9e3c8 feat: add params to mutation 2024-12-13 16:33:54 +01:00
djknaeckebrot a439286e5f feat: add UI selection 2024-12-13 16:14:52 +01:00
Nicholas Penree e5d5a98bab feat(logs): preserve whitespace in log line 2024-12-13 09:15:56 -05:00
Nicholas Penree 4311ba93f3 chore: lint/typecheck 2024-12-13 09:00:17 -05:00
djknaeckebrot e0b596ec76 chore: set static minio version 2024-12-13 14:14:22 +01:00
djknaeckebrot 379ba20930 feat: add huly.io template 2024-12-13 13:55:57 +01:00
djknaeckebrot 236e511adc feat: add langflow template 2024-12-13 12:02:01 +01:00
djknaeckebrot 0b37e171c5 chore: add fixed versions 2024-12-13 09:20:23 +01:00
Mauricio Siu 1df1e7b50b Merge pull request #870 from drudge/setup
fix(setup/validate): arm64 build fixes, improved validation
2024-12-13 01:07:13 -06:00
Mauricio Siu f15a5bc22d Update apps/dokploy/templates/unsend/index.ts 2024-12-13 00:50:12 -06:00
Mauricio Siu 469871d383 Update apps/dokploy/templates/unsend/docker-compose.yml 2024-12-13 00:50:08 -06:00
Mauricio Siu e22b6ab9be Update apps/dokploy/templates/unsend/docker-compose.yml 2024-12-13 00:50:04 -06:00
Mauricio Siu b01b05077d Update apps/dokploy/templates/unsend/docker-compose.yml 2024-12-13 00:49:58 -06:00
Mauricio Siu 22122361ba Update apps/dokploy/templates/unsend/docker-compose.yml 2024-12-13 00:49:54 -06:00
Mauricio Siu 87c1ce68b9 Update apps/dokploy/templates/unsend/docker-compose.yml 2024-12-13 00:49:49 -06:00
Mauricio Siu 4c8619677b Update apps/dokploy/templates/unsend/docker-compose.yml 2024-12-13 00:49:45 -06:00
Mauricio Siu 7f705e31d3 Merge pull request #867 from DJKnaeckebrot/feat/add-vscode-settings-for-same-formatting
format: setup .vscode folder with biome.js for consistent format and spacing
2024-12-13 00:39:18 -06:00
Mauricio Siu 20432ebc3f Merge pull request #856 from DJKnaeckebrot/feat/project-search
feat: add global search command
2024-12-13 00:38:07 -06:00
Nicholas Penree a51a7a82d2 feat(setup): remove debconf warnings during setup 2024-12-12 23:58:56 -05:00
Nicholas Penree 5ba19686c8 feat(setup): align pass/fail icons at the end 2024-12-12 23:57:54 -05:00
Nicholas Penree 22a2e64563 feat(logs): tooltip improvements (break out, no delay) 2024-12-12 23:12:13 -05:00
190km 37ee89e6ab fix: debug value in select 2024-12-13 00:53:58 +01:00
190km cb487b8be0 feat: added debug log type & noTimestamp props for TerminalLine 2024-12-12 21:53:10 +01:00
DJKnaeckebrot 26f8719e5f fix: wrong port publishing 2024-12-12 21:41:57 +01:00
190km 3bc1bd5b15 feat: added more log success filter 2024-12-12 21:10:19 +01:00
190km ee622b1ba0 feat: added new logs styling in deployments views 2024-12-12 21:00:17 +01:00
190km fe088bad3b feat: add loading spinner when logs are being loaded 2024-12-12 19:54:44 +01:00
usopp d374f5eedf fix: no time found block same width as the timestamp ones 2024-12-12 19:28:16 +01:00
Nicholas Penree 1c498ee2d2 fix(setup/validate): arm64 build fixes, improved validation 2024-12-12 12:11:30 -05:00
djknaeckebrot d7e5eb6dfd feat: add unsend template 2024-12-12 16:46:58 +01:00
djknaeckebrot f71e04eaaa feat: add page titles 2024-12-12 12:56:55 +01:00
djknaeckebrot fc9808e295 feat: added .vscode folder
Added settings.json to use biome.js formatter by default
Added extentions.json to recommend to install biome.js

This all is to keep spacing the same across different contributors and to make it easier for people to get started contributing
2024-12-12 11:56:57 +01:00
djknaeckebrot bc2a286e1d fix: close modal after selection of item 2024-12-12 08:48:23 +01:00
djknaeckebrot 6c582eb91d chore: close when project or app is selected
chore: better closing checks
fix: url for projects
2024-12-12 08:43:41 +01:00
djknaeckebrot e3b2a401a7 fix: resolve issue with same names 2024-12-12 08:39:29 +01:00
djknaeckebrot 6c55143e96 chore: remove debug logging 2024-12-12 08:33:23 +01:00
djknaeckebrot 19a0550b32 style: change status indication 2024-12-12 08:32:18 +01:00
Mauricio Siu bb31bef8bc Merge pull request #865 from drudge/config-code-editor
feat(docker): use code editor when displaying container config
2024-12-11 23:31:21 -06:00
Mauricio Siu abc606d8d9 Merge pull request #864 from mezotv/dynamic-timestamp
feat(dynamic timestamp): add dynamic discord timestamps
2024-12-11 23:28:47 -06:00
Mauricio Siu 749dd03fe6 Merge pull request #859 from kawws/feature/github-app
feat: improve github app validation
2024-12-11 23:25:27 -06:00
Mauricio Siu 858d7e5c11 Merge pull request #863 from 190km/italian-lang
feat(i18n): add italian language support
2024-12-11 23:12:31 -06:00
Mauricio Siu 079b7b8e72 Merge branch 'canary' into italian-lang 2024-12-11 23:11:22 -06:00
Nicholas Penree 179f3818f0 feat(docker): use code editor when displaying container config 2024-12-11 22:53:10 -05:00
Nicholas Penree 8546031df0 feat(logs): lint 2024-12-11 19:32:34 -05:00
Nicholas Penree 16ca198eb4 feat(logs): better download file names 2024-12-11 19:31:37 -05:00
Nicholas Penree 9b5b452d90 Merge pull request #2 from drudge/log-test
feat(logs): improvements based on feedback
2024-12-11 19:23:45 -05:00
Nicholas Penree 2fa6f3bfa6 feat(logs): lint 2024-12-11 19:20:30 -05:00
Nicholas Penree 42f3105f69 feat(logs): improvements based on feedback 2024-12-11 19:13:53 -05:00
Dominik Koch a08ba7e8b5 feat(dynamic timestamp): add dynamic discord timestamps 2024-12-11 21:18:01 +00:00
190km a51ada4a1e feat(i18n): add italian language support 2024-12-11 20:39:59 +01:00
190km 50b1de9594 feat: added appname as filename when export 2024-12-11 20:26:19 +01:00
190km cb90281583 feat: added appname as filename when export 2024-12-11 20:25:49 +01:00
usopp 20b253e708 removed useless state 2024-12-11 18:03:28 +01:00
190km 9a51e0a00d show a message about no matches found 2024-12-11 17:58:35 +01:00
190km 49b812e462 Merge pull request #1 from drudge/log-test
feat(logs): improvements to searching
2024-12-11 16:49:05 +01:00
Nicholas Penree 7233667d49 feat(logs): improvements to searching 2024-12-11 10:35:12 -05:00
Nicholas Penree 95cd410825 feat(logs): improvements to searching 2024-12-11 10:29:09 -05:00
Andreassenemyr 5b8ebdaaa4 feat: improve github app validation 2024-12-11 13:31:07 +01:00
djknaeckebrot 343d5ae6a2 feat: add service status 2024-12-11 08:27:18 +01:00
djknaeckebrot e16ce0c817 refactor: only show Monitoring, Traefik, Docker and Requests when installation is not a cloud installation 2024-12-11 07:36:23 +01:00
Mauricio Siu 3b2440b1db Merge pull request #835 from Dokploy/560-self-hosted-gitlab
560 self hosted gitlab
2024-12-10 22:07:59 -06:00
Mauricio Siu 2f72ccbea7 Merge pull request #855 from hua1995116/fix/memory-bug
fix: error about memoryReservation not set
2024-12-10 21:55:41 -06:00
Mauricio Siu be47f6d09a Merge pull request #848 from minagishl/feat/i18n-ja
feat(i18n): add japanese language support
2024-12-10 21:54:06 -06:00
Mauricio Siu 9a65bf8e21 chore: remove unused file 2024-12-10 21:53:32 -06:00
Mauricio Siu 15959fa91f fix: add missing migration 2024-12-10 21:52:09 -06:00
Mauricio Siu f0c14d144c Merge branch 'canary' into 560-self-hosted-gitlab 2024-12-10 21:50:09 -06:00
Mauricio Siu 725b763aa8 Merge pull request #854 from DJKnaeckebrot/560-self-hosted-gitlab
560 self hosted gitlab additions
2024-12-10 21:48:39 -06:00
Mauricio Siu c0bfd7dde7 Merge pull request #851 from drudge/n8n-template-update
chore: update n8n template to 1.70.3
2024-12-10 21:45:23 -06:00
Mauricio Siu 31ba5a784d Merge pull request #850 from drudge/logto-template
feat: add Logto template
2024-12-10 21:45:09 -06:00
190km 00f9e262a9 feat(logs): new logs style and system 2024-12-11 00:20:22 +01:00
djknaeckebrot 54b6a850b7 refactor: make command globally available 2024-12-10 14:15:39 +01:00
djknaeckebrot 029cbf4498 refactor: change location of search-command 2024-12-10 14:09:04 +01:00
hua1995116 8a1cba470c fix: error about memoryReservation not set 2024-12-10 20:40:26 +08:00
djknaeckebrot 84bb98c7e6 feat: add search command dialog with projects and services 2024-12-10 13:37:57 +01:00
djknaeckebrot cbf0f37a49 feat: add search command dialog with projects and services 2024-12-10 13:37:46 +01:00
djknaeckebrot 2c22aa3689 chore: remove debug logs 2024-12-10 10:25:48 +01:00
Mauricio Siu 46289305e8 Merge pull request #853 from Dokploy/canary
v0.14.1
2024-12-10 02:51:45 -06:00
Mauricio Siu 0b3e15aabc Merge pull request #852 from Dokploy/fix/nixpacks-version
fix: pin nixpacks version
2024-12-10 02:50:26 -06:00
Mauricio Siu 69a3583717 chore: bump version 2024-12-10 02:39:31 -06:00
Mauricio Siu bc55fde6d6 fix: pin nixpacks version 2024-12-10 02:38:50 -06:00
djknaeckebrot 1cb1da8097 fix: replace hardcoded gitlab.com with dynamic urls 2024-12-10 09:19:01 +01:00
djknaeckebrot 0698ac8318 chore: remove debug output 2024-12-10 08:54:22 +01:00
djknaeckebrot 7ced6840fa fix: change hardcoded gitlab.com to gitlabUrl 2024-12-10 08:53:53 +01:00
djknaeckebrot 22e6d07f60 fix: hardcoded gitlab.com to gitlabUrl 2024-12-10 08:37:55 +01:00
Nicholas Penree 6a9690fe3c chore: update n8n template to 1.70.3 2024-12-09 22:57:32 -05:00
Nicholas Penree 1c02478688 feat: add Logto template 2024-12-09 22:28:47 -05:00
minagishl bbe72ad584 feat(i18n): add japanese language support 2024-12-09 15:15:16 +09:00
Mauricio Siu 83b3176f6f Merge pull request #837 from wish-oss/fix/gpu-setup
fix: update GPU setup command to use sudo and add error handling
2024-12-08 22:09:23 -06:00
Mauricio Siu 7ecd1627c8 Merge pull request #845 from Dokploy/canary
v0.14.0
2024-12-08 20:59:38 -06:00
vishalkadam47 49559ebee6 fix: update GPU setup command to use sudo and add error handling 2024-12-08 06:27:34 +05:30
Mauricio Siu f94ee8c299 refactor: update 2024-12-07 16:50:25 -06:00
Mauricio Siu f47335efe5 refactor: wip 2024-12-07 16:47:39 -06:00
Mauricio Siu 572579af91 Merge pull request #785 from Dokploy/canary
v0.13.1
2024-11-28 23:44:33 -06:00
Mauricio Siu b296b6bbf0 Merge pull request #772 from Dokploy/canary
v0.13.0
2024-11-27 23:28:40 -06:00
Mauricio Siu 5c8721406a Merge pull request #720 from Dokploy/canary
v0.12.0
2024-11-17 22:07:33 -06:00
Mauricio Siu be934065d9 Merge pull request #702 from Dokploy/canary
v0.11.2
2024-11-13 23:48:47 -06:00
Mauricio Siu 8162dcfb71 Merge pull request #697 from limichange/patch-1
Update plausible docker-compose.yml
2024-11-13 23:34:57 -06:00
limichange 46f7d43595 Update templates.ts 2024-11-14 09:37:20 +08:00
limichange 59bb59ee24 Update docker-compose.yml 2024-11-13 16:30:11 +08:00
Mauricio Siu 0e433a3d36 Merge pull request #689 from Dokploy/canary
v0.11.1
2024-11-12 01:04:36 -06:00
Mauricio Siu 819de5a32e Merge pull request #677 from Dokploy/canary
v0.11.0
2024-11-10 19:28:28 -06:00
Mauricio Siu 5cd624c7ea Merge pull request #647 from Dokploy/canary
v0.10.10
2024-11-04 10:32:20 -06:00
Mauricio Siu 89c7e96df0 Merge pull request #643 from Dokploy/canary
v0.10.9
2024-11-02 17:18:34 -06:00
Mauricio Siu 0e136ffb8f Merge pull request #632 from Dokploy/canary
v10.7.0
2024-10-30 23:10:48 -06:00
Mauricio Siu 6b4e52fb37 Merge pull request #631 from Dokploy/canary
v0.10.6
2024-10-30 23:00:45 -06:00
Mauricio Siu db9136d981 Merge pull request #629 from Dokploy/canary
v0.10.5
2024-10-30 22:01:04 -06:00
Mauricio Siu a93f18eb4a Merge pull request #599 from Dokploy/canary
v0.10.4
2024-10-25 21:35:15 -06:00
Mauricio Siu 467acc4d4d Merge pull request #586 from Dokploy/canary
v0.10.3
2024-10-24 01:21:17 -06:00
Mauricio Siu 01e5cf0852 Merge pull request #580 from Dokploy/canary
v0.10.2
2024-10-22 21:17:38 -06:00
Mauricio Siu df9fad088f Merge pull request #567 from Dokploy/canary
v0.10.1
2024-10-18 23:12:12 -06:00
Mauricio Siu 2644b638d1 Merge pull request #553 from Dokploy/canary
v0.10.0
2024-10-13 16:30:00 -06:00
Mauricio Siu acd722678e Merge pull request #521 from Dokploy/canary
v0.9.4
2024-10-03 02:12:03 -06:00
Mauricio Siu 727e50648e Merge pull request #501 from Dokploy/canary
v0.9.3
2024-09-29 16:30:43 -06:00
Mauricio Siu 349bc89851 Merge pull request #500 from Dokploy/canary
v0.9.2
2024-09-29 14:16:56 -06:00
Mauricio Siu 9f6f872536 Merge pull request #495 from Dokploy/canary
v0.9.1
2024-09-28 12:00:04 -06:00
Mauricio Siu e378d89477 Merge pull request #475 from Dokploy/canary
v0.9.0
2024-09-22 19:38:12 -06:00
Mauricio Siu 63e7eacae9 chore(version): bump version 2024-09-19 16:37:00 -06:00
Mauricio Siu f4ab588516 Merge pull request #466 from Dokploy/canary
v0.8.3
2024-09-19 16:01:27 -06:00
Mauricio Siu 4d8a0ba58f Merge pull request #457 from Dokploy/canary
v0.8.2
2024-09-16 15:57:20 -06:00
Mauricio Siu e88cd11041 Merge pull request #427 from Dokploy/canary
v0.8.1
2024-09-07 13:25:36 -06:00
Mauricio Siu 5f174a883b Merge pull request #424 from Dokploy/canary
v0.8.0
2024-09-07 00:55:08 -06:00
Mauricio Siu 536a6ba2ff Merge pull request #397 from Dokploy/canary
v0.7.3
2024-08-30 00:27:59 -06:00
Mauricio Siu 213fa08210 Merge pull request #382 from Dokploy/canary
v0.7.2
2024-08-26 15:52:49 -06:00
Mauricio Siu d5c6a601d8 Merge pull request #367 from Dokploy/canary
v0.7.1
2024-08-19 16:03:39 -06:00
Mauricio Siu 452793c8e5 Merge pull request #359 from Dokploy/canary
v0.7.0
2024-08-18 10:26:52 -06:00
Mauricio Siu 385fbf4af5 Merge pull request #355 from Dokploy/canary
v0.6.3
2024-08-16 22:26:35 -06:00
Mauricio Siu 3590f3bed2 Merge pull request #332 from Dokploy/canary
v0.6.2
2024-08-07 21:48:49 -06:00
Mauricio Siu 9b2fcaea31 Merge pull request #317 from Dokploy/canary
v0.6.1
2024-08-03 15:46:05 -06:00
Mauricio Siu 5abcc82215 Merge pull request #312 from Dokploy/canary
v0.6.0
2024-08-02 10:47:43 -06:00
Mauricio Siu ee855452e3 Merge pull request #303 from Dokploy/canary
chore: add slash to version
2024-08-01 02:06:43 -06:00
Mauricio Siu d000b526d3 Merge pull request #302 from Dokploy/canary
v0.5.1
2024-08-01 01:58:15 -06:00
Mauricio Siu 9bf88b90c3 Merge pull request #280 from Dokploy/canary
v0.5.0
2024-07-27 15:20:43 -06:00
Mauricio Siu b1a48d4636 refactor: update job 2024-07-22 03:51:07 -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 bb59a0cd3f Merge pull request #230 from Dokploy/canary
v0.3.3
2024-07-18 00:11:10 -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 bfdc73f8d1 Merge pull request #197 from Dokploy/canary
v0.3.1
2024-07-06 12:01:07 -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 4706adc0c0 Merge pull request #174 from Dokploy/canary
v0.2.5
2024-06-29 13:29:39 -06:00
Mauricio Siu e01d92d1d9 Merge pull request #161 from Dokploy/canary
v0.2.4
2024-06-23 19:40:45 -06:00
Mauricio Siu fe22890311 Merge pull request #156 from Dokploy/canary
v0.2.3
2024-06-21 11:50:40 -06:00
Mauricio Siu 2b7c7632f4 Merge pull request #136 from Dokploy/canary
v0.2.2
2024-06-08 22:06:39 -06:00
Mauricio Siu 1b7244e841 Merge pull request #127 from Dokploy/canary
v0.2.1
2024-06-07 02:52:03 -06:00
413 changed files with 27696 additions and 2462 deletions
+3 -3
View File
@@ -99,14 +99,14 @@ workflows:
only:
- main
- canary
- 379-preview-deployment
- fix/nixpacks-version
- build-arm64:
filters:
branches:
only:
- main
- canary
- 379-preview-deployment
- fix/nixpacks-version
- combine-manifests:
requires:
- build-amd64
@@ -116,4 +116,4 @@ workflows:
only:
- main
- canary
- 379-preview-deployment
- fix/nixpacks-version
-1
View File
@@ -34,7 +34,6 @@ yarn-debug.log*
yarn-error.log*
# Editor
.vscode
.idea
# Misc
+2
View File
@@ -48,6 +48,8 @@ RUN curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && rm
# Install Nixpacks and tsx
# | VERBOSE=1 VERSION=1.21.0 bash
ARG NIXPACKS_VERSION=1.29.1
RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
&& chmod +x install.sh \
&& ./install.sh \
+1 -1
View File
@@ -87,7 +87,7 @@ export const Login2FA = ({ authId }: Props) => {
</span>
</div>
)}
<CardTitle className="text-xl font-bold">2FA Setup</CardTitle>
<CardTitle className="text-xl font-bold">2FA Login</CardTitle>
<FormField
control={form.control}
@@ -259,7 +259,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
refetch();
})
.catch(() => {
toast.error("Error to update the swarm settings");
toast.error("Error updating the swarm settings");
});
};
return (
@@ -94,7 +94,7 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
});
})
.catch(() => {
toast.error("Error to update the command");
toast.error("Error updating the command");
});
};
@@ -71,7 +71,7 @@ export const AddCommand = ({ applicationId }: Props) => {
});
})
.catch(() => {
toast.error("Error to update the command");
toast.error("Error updating the command");
});
};
@@ -86,7 +86,7 @@ export const AddPort = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the port");
toast.error("Error creating the port");
});
};
@@ -47,10 +47,10 @@ export const DeletePort = ({ portId }: Props) => {
applicationId: data?.applicationId,
});
toast.success("Port delete succesfully");
toast.success("Port delete successfully");
})
.catch(() => {
toast.error("Error to delete the port");
toast.error("Error deleting the port");
});
}}
>
@@ -93,7 +93,7 @@ export const UpdatePort = ({ portId }: Props) => {
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the port");
toast.error("Error updating the port");
});
};
@@ -124,7 +124,7 @@ export const AddRedirect = ({
onDialogToggle(false);
})
.catch(() => {
toast.error("Error to create the redirect");
toast.error("Error creating the redirect");
});
};
@@ -50,10 +50,10 @@ export const DeleteRedirect = ({ redirectId }: Props) => {
utils.application.readTraefikConfig.invalidate({
applicationId: data?.applicationId,
});
toast.success("Redirect delete succesfully");
toast.success("Redirect delete successfully");
})
.catch(() => {
toast.error("Error to delete the redirect");
toast.error("Error deleting the redirect");
});
}}
>
@@ -88,7 +88,7 @@ export const UpdateRedirect = ({ redirectId }: Props) => {
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the redirect");
toast.error("Error updating the redirect");
});
};
@@ -75,7 +75,7 @@ export const AddSecurity = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the security");
toast.error("Error creating security");
});
};
@@ -50,10 +50,10 @@ export const DeleteSecurity = ({ securityId }: Props) => {
utils.application.readTraefikConfig.invalidate({
applicationId: data?.applicationId,
});
toast.success("Security delete succesfully");
toast.success("Security delete successfully");
})
.catch(() => {
toast.error("Error to delete the security");
toast.error("Error deleting the security");
});
}}
>
@@ -83,7 +83,7 @@ export const UpdateSecurity = ({ securityId }: Props) => {
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the security");
toast.error("Error updating the security");
});
};
@@ -22,6 +22,13 @@ import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { InfoIcon } from "lucide-react";
const addResourcesApplication = z.object({
memoryReservation: z.number().nullable().optional(),
@@ -72,7 +79,7 @@ export const ShowApplicationResources = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to Update the resources");
toast.error("Error updating the resources");
});
};
return (
@@ -101,10 +108,25 @@ export const ShowApplicationResources = ({ applicationId }: Props) => {
name="memoryReservation"
render={({ field }) => (
<FormItem>
<FormLabel>Memory Reservation</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>Memory Reservation</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
Memory soft limit in bytes. Example: 256MB =
268435456 bytes
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder="256 MB"
placeholder="268435456 (256MB in bytes)"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
@@ -120,7 +142,6 @@ export const ShowApplicationResources = ({ applicationId }: Props) => {
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
@@ -132,10 +153,25 @@ export const ShowApplicationResources = ({ applicationId }: Props) => {
render={({ field }) => {
return (
<FormItem>
<FormLabel>Memory Limit</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>Memory Limit</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
Memory hard limit in bytes. Example: 1GB =
1073741824 bytes
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder={"1024 MB"}
placeholder="1073741824 (1GB in bytes)"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
@@ -163,21 +199,36 @@ export const ShowApplicationResources = ({ applicationId }: Props) => {
render={({ field }) => {
return (
<FormItem>
<FormLabel>Cpu Limit</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>CPU Limit</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
CPU quota in units of 10^-9 CPUs. Example: 2
CPUs = 2000000000
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder={"2"}
placeholder="2000000000 (2 CPUs)"
{...field}
type="number"
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (
value === "" ||
/^[0-9]*\.?[0-9]*$/.test(value)
) {
const float = Number.parseFloat(value);
field.onChange(float);
if (value === "") {
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
field.onChange(number);
}
}
}}
/>
@@ -193,21 +244,36 @@ export const ShowApplicationResources = ({ applicationId }: Props) => {
render={({ field }) => {
return (
<FormItem>
<FormLabel>Cpu Reservation</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>CPU Reservation</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
CPU shares (relative weight). Example: 1 CPU =
1000000000
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder={"1"}
placeholder="1000000000 (1 CPU)"
{...field}
type="number"
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (
value === "" ||
/^[0-9]*\.?[0-9]*$/.test(value)
) {
const float = Number.parseFloat(value);
field.onChange(float);
if (value === "") {
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
field.onChange(number);
}
}
}}
/>
@@ -105,7 +105,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
form.reset();
})
.catch(() => {
toast.error("Error to update the traefik config");
toast.error("Error updating the Traefik config");
});
};
@@ -107,7 +107,7 @@ export const AddVolumes = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the Bind mount");
toast.error("Error creating the Bind mount");
});
} else if (data.type === "volume") {
await mutateAsync({
@@ -122,7 +122,7 @@ export const AddVolumes = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the Volume mount");
toast.error("Error creating the Volume mount");
});
} else if (data.type === "file") {
await mutateAsync({
@@ -138,7 +138,7 @@ export const AddVolumes = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the File mount");
toast.error("Error creating the File mount");
});
}
@@ -45,10 +45,10 @@ export const DeleteVolume = ({ mountId, refetch }: Props) => {
})
.then(() => {
refetch();
toast.success("Mount deleted succesfully");
toast.success("Mount deleted successfully");
})
.catch(() => {
toast.error("Error to delete the mount");
toast.error("Error deleting the mount");
});
}}
>
@@ -139,7 +139,7 @@ export const UpdateVolume = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the Bind mount");
toast.error("Error updating the Bind mount");
});
} else if (data.type === "volume") {
await mutateAsync({
@@ -153,7 +153,7 @@ export const UpdateVolume = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the Volume mount");
toast.error("Error updating the Volume mount");
});
} else if (data.type === "file") {
await mutateAsync({
@@ -168,7 +168,7 @@ export const UpdateVolume = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the File mount");
toast.error("Error updating the File mount");
});
}
refetch();
@@ -126,7 +126,7 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the build type");
toast.error("Error saving the build type");
});
};
@@ -1,3 +1,4 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -19,7 +20,7 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { Copy, TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
@@ -102,9 +103,26 @@ export const DeleteApplication = ({ applicationId }: Props) => {
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
<FormLabel className="flex items-center gap-2">
<span>
To confirm, type{" "}
<Badge
className="p-2 rounded-md ml-1 mr-1 hover:border-primary hover:text-primary-foreground hover:bg-primary hover:cursor-pointer"
variant="outline"
onClick={() => {
if (data?.name && data?.appName) {
navigator.clipboard.writeText(
`${data.name}/${data.appName}`,
);
toast.success("Copied to clipboard. Be careful!");
}
}}
>
{data?.name}/{data?.appName}&nbsp;
<Copy className="h-4 w-4 ml-1 text-muted-foreground" />
</Badge>{" "}
in the box below:
</span>
</FormLabel>
<FormControl>
<Input
@@ -47,7 +47,7 @@ export const RefreshToken = ({ applicationId }: Props) => {
toast.success("Refresh updated");
})
.catch(() => {
toast.error("Error to update the refresh token");
toast.error("Error updating the refresh token");
});
}}
>
@@ -1,3 +1,5 @@
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
@@ -5,7 +7,10 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Loader2 } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { TerminalLine } from "../../docker/logs/terminal-line";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
interface Props {
logPath: string | null;
@@ -15,8 +20,25 @@ interface Props {
}
export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
const [data, setData] = useState("");
const endOfLogsRef = useRef<HTMLDivElement>(null);
const [showExtraLogs, setShowExtraLogs] = useState(false);
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
const wsRef = useRef<WebSocket | null>(null); // Ref to hold WebSocket instance
const [autoScroll, setAutoScroll] = useState(true);
const scrollRef = useRef<HTMLDivElement>(null);
const scrollToBottom = () => {
if (autoScroll && scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
};
const handleScroll = () => {
if (!scrollRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
setAutoScroll(isAtBottom);
};
useEffect(() => {
if (!open || !logPath) return;
@@ -48,13 +70,34 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
};
}, [logPath, open]);
const scrollToBottom = () => {
endOfLogsRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
const logs = parseLogs(data);
let filteredLogsResult = logs;
if (serverId) {
let hideSubsequentLogs = false;
filteredLogsResult = logs.filter((log) => {
if (
log.message.includes(
"===================================EXTRA LOGS============================================",
)
) {
hideSubsequentLogs = true;
return showExtraLogs;
}
return showExtraLogs ? true : !hideSubsequentLogs;
});
}
setFilteredLogs(filteredLogsResult);
}, [data, showExtraLogs]);
useEffect(() => {
scrollToBottom();
}, [data]);
if (autoScroll && scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [filteredLogs, autoScroll]);
return (
<Dialog
@@ -75,18 +118,49 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}>
<DialogHeader>
<DialogTitle>Deployment</DialogTitle>
<DialogDescription>
See all the details of this deployment
<DialogDescription className="flex items-center gap-2">
<span>
See all the details of this deployment |{" "}
<Badge variant="blank" className="text-xs">
{filteredLogs.length} lines
</Badge>
</span>
{serverId && (
<div className="flex items-center space-x-2">
<Checkbox
id="show-extra-logs"
checked={showExtraLogs}
onCheckedChange={(checked) =>
setShowExtraLogs(checked as boolean)
}
/>
<label
htmlFor="show-extra-logs"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Show Extra Logs
</label>
</div>
)}
</DialogDescription>
</DialogHeader>
<div className="text-wrap rounded-lg border p-4 text-sm sm:max-w-[59rem]">
<code>
<pre className="whitespace-pre-wrap break-words">
{data || "Loading..."}
</pre>
<div ref={endOfLogsRef} />
</code>
<div
ref={scrollRef}
onScroll={handleScroll}
className="h-[720px] overflow-y-auto space-y-0 border p-4 bg-[#fafafa] dark:bg-[#050506] rounded custom-logs-scrollbar"
>
{" "}
{filteredLogs.length > 0 ? (
filteredLogs.map((log: LogLine, index: number) => (
<TerminalLine key={index} log={log} noTimestamp />
))
) : (
<div className="flex justify-center items-center h-full text-muted-foreground">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
)}
</div>
</DialogContent>
</Dialog>
@@ -105,8 +105,8 @@ export const AddDomain = ({
const dictionary = {
success: domainId ? "Domain Updated" : "Domain Created",
error: domainId
? "Error to update the domain"
: "Error to create the domain",
? "Error updating the domain"
: "Error creating the domain",
submit: domainId ? "Update" : "Create",
dialogDescription: domainId
? "In this section you can edit a domain"
@@ -264,21 +264,21 @@ export const AddDomain = ({
name="certificateType"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Certificate</FormLabel>
<FormLabel>Certificate Provider</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a certificate" />
<SelectValue placeholder="Select a certificate provider" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem value={"letsencrypt"}>
Letsencrypt (Default)
Let's Encrypt
</SelectItem>
</SelectContent>
</Select>
@@ -57,10 +57,10 @@ export const DeleteDomain = ({ domainId }: Props) => {
});
}
toast.success("Domain delete succesfully");
toast.success("Domain delete successfully");
})
.catch(() => {
toast.error("Error to delete Domain");
toast.error("Error deleting the Domain");
});
}}
>
@@ -51,7 +51,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to add environment");
toast.error("Error adding environment");
});
};
@@ -11,6 +11,7 @@ import {
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { useRouter } from "next/router";
import { toast } from "sonner";
interface Props {
@@ -18,6 +19,7 @@ interface Props {
}
export const DeployApplication = ({ applicationId }: Props) => {
const router = useRouter();
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
@@ -49,12 +51,15 @@ export const DeployApplication = ({ applicationId }: Props) => {
applicationId,
})
.then(async () => {
toast.success("Application deployed succesfully");
toast.success("Application deployed successfully");
await refetch();
router.push(
`/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`,
);
})
.catch(() => {
toast.error("Error to deploy Application");
toast.error("Error deploying the Application");
});
await refetch();
@@ -137,7 +137,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the Bitbucket provider");
toast.error("Error saving the Bitbucket provider");
});
};
@@ -68,7 +68,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the Docker provider");
toast.error("Error saving the Docker provider");
});
};
@@ -56,7 +56,7 @@ export const SaveDragNDrop = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the deployment");
toast.error("Error saving the deployment");
});
};
@@ -84,7 +84,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the Git provider");
toast.error("Error saving the Git provider");
});
};
@@ -131,7 +131,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the github provider");
toast.error("Error saving the github provider");
});
};
@@ -144,7 +144,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the gitlab provider");
toast.error("Error saving the gitlab provider");
});
};
@@ -56,7 +56,7 @@ export const ResetApplication = ({ applicationId, appName }: Props) => {
toast.success("Service Reloaded");
})
.catch(() => {
toast.error("Error to reload the service");
toast.error("Error reloading the service");
});
await refetch();
}}
@@ -69,7 +69,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to update Auto Deploy");
toast.error("Error updating Auto Deploy");
});
}}
className="flex flex-row gap-2 items-center"
@@ -1,3 +1,4 @@
import { Badge } from "@/components/ui/badge";
import {
Card,
CardContent,
@@ -15,6 +16,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
@@ -29,28 +31,67 @@ export const DockerLogs = dynamic(
},
);
export const badgeStateColor = (state: string) => {
switch (state) {
case "running":
return "green";
case "exited":
case "shutdown":
return "red";
case "accepted":
case "created":
return "blue";
default:
return "default";
}
};
interface Props {
appName: string;
serverId?: string;
}
export const ShowDockerLogs = ({ appName, serverId }: Props) => {
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
serverId,
},
{
enabled: !!appName,
},
);
const [containerId, setContainerId] = useState<string | undefined>();
const [option, setOption] = useState<"swarm" | "native">("native");
const { data: services, isLoading: servicesLoading } =
api.docker.getServiceContainersByAppName.useQuery(
{
appName,
serverId,
},
{
enabled: !!appName && option === "swarm",
},
);
const { data: containers, isLoading: containersLoading } =
api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
serverId,
},
{
enabled: !!appName && option === "native",
},
);
useEffect(() => {
if (data && data?.length > 0) {
setContainerId(data[0]?.containerId);
if (option === "native") {
if (containers && containers?.length > 0) {
setContainerId(containers[0]?.containerId);
}
} else {
if (services && services?.length > 0) {
setContainerId(services[0]?.containerId);
}
}
}, [data]);
}, [option, services, containers]);
const isLoading = option === "native" ? containersLoading : servicesLoading;
const containersLenght =
option === "native" ? containers?.length : services?.length;
return (
<Card className="bg-background">
@@ -62,7 +103,21 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => {
</CardHeader>
<CardContent className="flex flex-col gap-4">
<Label>Select a container to view logs</Label>
<div className="flex flex-row justify-between items-center gap-2">
<Label>Select a container to view logs</Label>
<div className="flex flex-row gap-2 items-center">
<span className="text-sm text-muted-foreground">
{option === "native" ? "Native" : "Swarm"}
</span>
<Switch
checked={option === "native"}
onCheckedChange={(checked) => {
setOption(checked ? "native" : "swarm");
}}
/>
</div>
</div>
<Select onValueChange={setContainerId} value={containerId}>
<SelectTrigger>
{isLoading ? (
@@ -76,22 +131,45 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => {
</SelectTrigger>
<SelectContent>
<SelectGroup>
{data?.map((container) => (
<SelectItem
key={container.containerId}
value={container.containerId}
>
{container.name} ({container.containerId}) {container.state}
</SelectItem>
))}
<SelectLabel>Containers ({data?.length})</SelectLabel>
{option === "native" ? (
<div>
{containers?.map((container) => (
<SelectItem
key={container.containerId}
value={container.containerId}
>
{container.name} ({container.containerId}){" "}
<Badge variant={badgeStateColor(container.state)}>
{container.state}
</Badge>
</SelectItem>
))}
</div>
) : (
<>
{services?.map((container) => (
<SelectItem
key={container.containerId}
value={container.containerId}
>
{container.name} ({container.containerId}@{container.node}
)
<Badge variant={badgeStateColor(container.state)}>
{container.state}
</Badge>
</SelectItem>
))}
</>
)}
<SelectLabel>Containers ({containersLenght})</SelectLabel>
</SelectGroup>
</SelectContent>
</Select>
<DockerLogs
serverId={serverId || ""}
id="terminal"
containerId={containerId || "select-a-container"}
runType={option}
/>
</CardContent>
</Card>
@@ -105,8 +105,8 @@ export const AddPreviewDomain = ({
const dictionary = {
success: domainId ? "Domain Updated" : "Domain Created",
error: domainId
? "Error to update the domain"
: "Error to create the domain",
? "Error updating the domain"
: "Error creating the domain",
submit: domainId ? "Update" : "Create",
dialogDescription: domainId
? "In this section you can edit a domain"
@@ -265,21 +265,21 @@ export const AddPreviewDomain = ({
name="certificateType"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Certificate</FormLabel>
<FormLabel>Certificate Provider</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a certificate" />
<SelectValue placeholder="Select a certificate provider" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem value={"letsencrypt"}>
Letsencrypt (Default)
Let's Encrypt
</SelectItem>
</SelectContent>
</Select>
@@ -18,15 +18,26 @@ import { ShowDeployment } from "../deployments/show-deployment";
interface Props {
deployments: RouterOutputs["deployment"]["all"];
serverId?: string;
trigger?: React.ReactNode;
}
export const ShowPreviewBuilds = ({ deployments, serverId }: Props) => {
export const ShowPreviewBuilds = ({
deployments,
serverId,
trigger,
}: Props) => {
const [activeLog, setActiveLog] = useState<string | null>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="outline">View Builds</Button>
{trigger ? (
trigger
) : (
<Button className="sm:w-auto w-full" size="sm" variant="outline">
View Builds
</Button>
)}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl">
<DialogHeader>
@@ -1,5 +1,8 @@
import { GithubIcon } from "@/components/icons/data-tools-icons";
import { DateTooltip } from "@/components/shared/date-tooltip";
import { DialogAction } from "@/components/shared/dialog-action";
import { StatusTooltip } from "@/components/shared/status-tooltip";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -8,30 +11,34 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Switch } from "@/components/ui/switch";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { Pencil, RocketIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
import {
ExternalLink,
FileText,
GitPullRequest,
Layers,
PenSquare,
RocketIcon,
Trash2,
} from "lucide-react";
import React from "react";
import { toast } from "sonner";
import { ShowDeployment } from "../deployments/show-deployment";
import Link from "next/link";
import { ShowModalLogs } from "../../settings/web-server/show-modal-logs";
import { DialogAction } from "@/components/shared/dialog-action";
import { AddPreviewDomain } from "./add-preview-domain";
import { GithubIcon } from "@/components/icons/data-tools-icons";
import { ShowPreviewSettings } from "./show-preview-settings";
import { ShowPreviewBuilds } from "./show-preview-builds";
import { ShowPreviewSettings } from "./show-preview-settings";
interface Props {
applicationId: string;
}
export const ShowPreviewDeployments = ({ applicationId }: Props) => {
const [activeLog, setActiveLog] = useState<string | null>(null);
const { data } = api.application.one.useQuery({ applicationId });
const { mutateAsync: deletePreviewDeployment, isLoading } =
api.previewDeployment.delete.useMutation();
const { data: previewDeployments, refetch: refetchPreviewDeployments } =
api.previewDeployment.all.useQuery(
{ applicationId },
@@ -39,10 +46,19 @@ export const ShowPreviewDeployments = ({ applicationId }: Props) => {
enabled: !!applicationId,
},
);
// const [url, setUrl] = React.useState("");
// useEffect(() => {
// setUrl(document.location.origin);
// }, []);
const handleDeletePreviewDeployment = async (previewDeploymentId: string) => {
deletePreviewDeployment({
previewDeploymentId: previewDeploymentId,
})
.then(() => {
refetchPreviewDeployments();
toast.success("Preview deployment deleted");
})
.catch((error) => {
toast.error(error.message);
});
};
return (
<Card className="bg-background">
@@ -65,7 +81,7 @@ export const ShowPreviewDeployments = ({ applicationId }: Props) => {
each pull request you create.
</span>
</div>
{data?.previewDeployments?.length === 0 ? (
{!previewDeployments?.length ? (
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
<RocketIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
@@ -74,120 +90,131 @@ export const ShowPreviewDeployments = ({ applicationId }: Props) => {
</div>
) : (
<div className="flex flex-col gap-4">
{previewDeployments?.map((previewDeployment) => {
const { deployments, domain } = previewDeployment;
{previewDeployments?.map((deployment) => {
const deploymentUrl = `${deployment.domain?.https ? "https" : "http"}://${deployment.domain?.host}${deployment.domain?.path || "/"}`;
const status = deployment.previewStatus;
return (
<div
key={previewDeployment?.previewDeploymentId}
className="flex flex-col justify-between rounded-lg border p-4 gap-2"
key={deployment.previewDeploymentId}
className="group relative overflow-hidden border rounded-lg transition-colors"
>
<div className="flex justify-between gap-2 max-sm:flex-wrap">
<div className="flex flex-col gap-2">
{deployments?.length === 0 ? (
<div>
<span className="text-sm text-muted-foreground">
No deployments found
</span>
</div>
) : (
<div className="flex items-center gap-2">
<span className="flex items-center gap-4 font-medium capitalize text-foreground">
{previewDeployment?.pullRequestTitle}
</span>
<StatusTooltip
status={previewDeployment.previewStatus}
className="size-2.5"
/>
</div>
)}
<div className="flex flex-col gap-1">
{previewDeployment?.pullRequestTitle && (
<div className="flex items-center gap-2">
<span className="break-all text-sm text-muted-foreground w-fit">
Title: {previewDeployment?.pullRequestTitle}
</span>
</div>
)}
<div
className={`absolute left-0 top-0 w-1 h-full ${
status === "done"
? "bg-green-500"
: status === "running"
? "bg-yellow-500"
: "bg-red-500"
}`}
/>
{previewDeployment?.pullRequestURL && (
<div className="flex items-center gap-2">
<GithubIcon />
<Link
target="_blank"
href={previewDeployment?.pullRequestURL}
className="break-all text-sm text-muted-foreground w-fit hover:underline hover:text-foreground"
>
Pull Request URL
</Link>
<div className="p-4">
<div className="flex items-start justify-between mb-3">
<div className="flex items-start gap-3">
<GitPullRequest className="size-5 text-muted-foreground mt-1 flex-shrink-0" />
<div>
<div className="font-medium text-sm">
{deployment.pullRequestTitle}
</div>
<div className="text-sm text-muted-foreground mt-1">
{deployment.branch}
</div>
)}
</div>
<div className="flex flex-col ">
<span>Domain </span>
<div className="flex flex-row items-center gap-4">
<Link
target="_blank"
href={`http://${domain?.host}`}
className="text-sm text-muted-foreground w-fit hover:underline hover:text-foreground"
>
{domain?.host}
</Link>
<AddPreviewDomain
previewDeploymentId={
previewDeployment.previewDeploymentId
}
domainId={domain?.domainId}
>
<Button variant="outline" size="sm">
<Pencil className="size-4 text-muted-foreground" />
</Button>
</AddPreviewDomain>
</div>
</div>
<Badge variant="outline" className="gap-2">
<StatusTooltip
status={deployment.previewStatus}
className="size-2"
/>
<DateTooltip date={deployment.createdAt} />
</Badge>
</div>
<div className="flex flex-col sm:items-end gap-2 max-sm:w-full">
{previewDeployment?.createdAt && (
<div className="text-sm capitalize text-muted-foreground">
<DateTooltip
date={previewDeployment?.createdAt}
/>
</div>
)}
<ShowPreviewBuilds
deployments={previewDeployment?.deployments || []}
serverId={data?.serverId || ""}
/>
<div className="pl-8 space-y-3">
<div className="relative flex-grow">
<Input
value={deploymentUrl}
readOnly
className="pr-8 text-sm text-blue-500 hover:text-blue-600 cursor-pointer"
onClick={() =>
window.open(deploymentUrl, "_blank")
}
/>
<ExternalLink className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-gray-400" />
</div>
<ShowModalLogs
appName={previewDeployment.appName}
serverId={data?.serverId || ""}
>
<Button variant="outline">View Logs</Button>
</ShowModalLogs>
<DialogAction
title="Delete Preview"
description="Are you sure you want to delete this preview?"
onClick={() => {
deletePreviewDeployment({
previewDeploymentId:
previewDeployment.previewDeploymentId,
})
.then(() => {
refetchPreviewDeployments();
toast.success("Preview deployment deleted");
})
.catch((error) => {
toast.error(error.message);
});
}}
>
<Button variant="destructive" isLoading={isLoading}>
Delete Preview
<div className="flex gap-2 opacity-80 group-hover:opacity-100 transition-opacity">
<Button
variant="outline"
size="sm"
className="gap-2"
onClick={() =>
window.open(deployment.pullRequestURL, "_blank")
}
>
<GithubIcon className="size-4" />
Pull Request
</Button>
</DialogAction>
<ShowModalLogs
appName={deployment.appName}
serverId={data?.serverId || ""}
>
<Button
variant="outline"
size="sm"
className="gap-2"
>
<FileText className="size-4" />
Logs
</Button>
</ShowModalLogs>
<ShowPreviewBuilds
deployments={deployment.deployments || []}
serverId={data?.serverId || ""}
trigger={
<Button
variant="outline"
size="sm"
className="gap-2"
>
<Layers className="size-4" />
Builds
</Button>
}
/>
<AddPreviewDomain
previewDeploymentId={`${deployment.previewDeploymentId}`}
domainId={deployment.domain?.domainId}
>
<Button
variant="ghost"
size="sm"
className="gap-2"
>
<PenSquare className="size-4" />
</Button>
</AddPreviewDomain>
<DialogAction
title="Delete Preview"
description="Are you sure you want to delete this preview?"
onClick={() =>
handleDeletePreviewDeployment(
deployment.previewDeploymentId,
)
}
>
<Button
variant="ghost"
size="sm"
isLoading={isLoading}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="size-4" />
</Button>
</DialogAction>
</div>
</div>
</div>
</div>
@@ -1,5 +1,3 @@
import { api } from "@/utils/api";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -20,12 +18,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input, NumberInput } from "@/components/ui/input";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Secrets } from "@/components/ui/secrets";
import { toast } from "sonner";
import { Switch } from "@/components/ui/switch";
import {
Select,
SelectContent,
@@ -33,6 +26,14 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Settings2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const schema = z.object({
env: z.string(),
@@ -116,7 +117,10 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
<div>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="outline">View Settings</Button>
<Button variant="outline">
<Settings2 className="size-4" />
Configure
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl w-full">
<DialogHeader>
@@ -218,21 +222,21 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
name="previewCertificateType"
render={({ field }) => (
<FormItem>
<FormLabel>Certificate</FormLabel>
<FormLabel>Certificate Provider</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a certificate" />
<SelectValue placeholder="Select a certificate provider" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem value={"letsencrypt"}>
Letsencrypt (Default)
Let's Encrypt
</SelectItem>
</SelectContent>
</Select>
@@ -63,7 +63,7 @@ export const RedbuildApplication = ({ applicationId }: Props) => {
});
})
.catch(() => {
toast.error("Error to rebuild the application");
toast.error("Error rebuilding the application");
});
}}
>
@@ -49,10 +49,10 @@ export const StartApplication = ({ applicationId }: Props) => {
await utils.application.one.invalidate({
applicationId,
});
toast.success("Application started succesfully");
toast.success("Application started successfully");
})
.catch(() => {
toast.error("Error to start the Application");
toast.error("Error starting the Application");
});
}}
>
@@ -49,10 +49,10 @@ export const StopApplication = ({ applicationId }: Props) => {
await utils.application.one.invalidate({
applicationId,
});
toast.success("Application stopped succesfully");
toast.success("Application stopped successfully");
})
.catch(() => {
toast.error("Error to stop the Application");
toast.error("Error stopping the Application");
});
}}
>
@@ -76,14 +76,14 @@ export const UpdateApplication = ({ applicationId }: Props) => {
description: formData.description || "",
})
.then(() => {
toast.success("Application updated succesfully");
toast.success("Application updated successfully");
utils.application.one.invalidate({
applicationId: applicationId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the application");
toast.error("Error updating the Application");
})
.finally(() => {});
};
@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -81,7 +82,7 @@ export const AddCommandCompose = ({ composeId }: Props) => {
});
})
.catch(() => {
toast.error("Error to update the command");
toast.error("Error updating the command");
});
};
@@ -91,7 +92,7 @@ export const AddCommandCompose = ({ composeId }: Props) => {
<div>
<CardTitle className="text-xl">Run Command</CardTitle>
<CardDescription>
Append a custom command to the compose file
Override a custom command to the compose file
</CardDescription>
</div>
</CardHeader>
@@ -101,6 +102,12 @@ export const AddCommandCompose = ({ composeId }: Props) => {
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
<AlertBlock type="warning">
Modifying the default command may affect deployment stability,
impacting logs and monitoring. Proceed carefully and test
thoroughly. By default, the command starts with{" "}
<strong>docker</strong>.
</AlertBlock>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
@@ -1,4 +1,6 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
@@ -11,6 +13,7 @@ import {
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
@@ -19,6 +22,7 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Copy } from "lucide-react";
import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
@@ -30,6 +34,7 @@ const deleteComposeSchema = z.object({
projectName: z.string().min(1, {
message: "Compose name is required",
}),
deleteVolumes: z.boolean(),
});
type DeleteCompose = z.infer<typeof deleteComposeSchema>;
@@ -49,6 +54,7 @@ export const DeleteCompose = ({ composeId }: Props) => {
const form = useForm<DeleteCompose>({
defaultValues: {
projectName: "",
deleteVolumes: false,
},
resolver: zodResolver(deleteComposeSchema),
});
@@ -56,7 +62,8 @@ export const DeleteCompose = ({ composeId }: Props) => {
const onSubmit = async (formData: DeleteCompose) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ composeId })
const { deleteVolumes } = formData;
await mutateAsync({ composeId, deleteVolumes })
.then((result) => {
push(`/dashboard/project/${result?.projectId}`);
toast.success("Compose deleted successfully");
@@ -100,10 +107,27 @@ export const DeleteCompose = ({ composeId }: Props) => {
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>{" "}
<FormLabel className="flex items-center gap-2">
<span>
To confirm, type{" "}
<Badge
className="p-2 rounded-md ml-1 mr-1 hover:border-primary hover:text-primary-foreground hover:bg-primary hover:cursor-pointer"
variant="outline"
onClick={() => {
if (data?.name && data?.appName) {
navigator.clipboard.writeText(
`${data.name}/${data.appName}`,
);
toast.success("Copied to clipboard. Be careful!");
}
}}
>
{data?.name}/{data?.appName}&nbsp;
<Copy className="h-4 w-4 ml-1 text-muted-foreground" />
</Badge>{" "}
in the box below:
</span>
</FormLabel>
<FormControl>
<Input
placeholder="Enter compose name to confirm"
@@ -114,6 +138,27 @@ export const DeleteCompose = ({ composeId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="deleteVolumes"
render={({ field }) => (
<FormItem>
<div className="flex items-center">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel className="ml-2">
Delete volumes associated with this compose
</FormLabel>
</div>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
@@ -47,7 +47,7 @@ export const RefreshTokenCompose = ({ composeId }: Props) => {
toast.success("Refresh Token updated");
})
.catch(() => {
toast.error("Error to update the refresh token");
toast.error("Error updating the refresh token");
});
}}
>
@@ -1,3 +1,5 @@
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
@@ -5,7 +7,10 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Loader2 } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { TerminalLine } from "../../docker/logs/terminal-line";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
interface Props {
logPath: string | null;
@@ -20,8 +25,25 @@ export const ShowDeploymentCompose = ({
serverId,
}: Props) => {
const [data, setData] = useState("");
const endOfLogsRef = useRef<HTMLDivElement>(null);
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
const [showExtraLogs, setShowExtraLogs] = useState(false);
const wsRef = useRef<WebSocket | null>(null); // Ref to hold WebSocket instance
const [autoScroll, setAutoScroll] = useState(true);
const scrollRef = useRef<HTMLDivElement>(null);
const scrollToBottom = () => {
if (autoScroll && scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
};
const handleScroll = () => {
if (!scrollRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
setAutoScroll(isAtBottom);
};
useEffect(() => {
if (!open || !logPath) return;
@@ -54,13 +76,34 @@ export const ShowDeploymentCompose = ({
};
}, [logPath, open]);
const scrollToBottom = () => {
endOfLogsRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
const logs = parseLogs(data);
let filteredLogsResult = logs;
if (serverId) {
let hideSubsequentLogs = false;
filteredLogsResult = logs.filter((log) => {
if (
log.message.includes(
"===================================EXTRA LOGS============================================",
)
) {
hideSubsequentLogs = true;
return showExtraLogs;
}
return showExtraLogs ? true : !hideSubsequentLogs;
});
}
setFilteredLogs(filteredLogsResult);
}, [data, showExtraLogs]);
useEffect(() => {
scrollToBottom();
}, [data]);
if (autoScroll && scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [filteredLogs, autoScroll]);
return (
<Dialog
@@ -78,21 +121,50 @@ export const ShowDeploymentCompose = ({
}
}}
>
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}>
<DialogContent className={"sm:max-w-5xl max-h-screen"}>
<DialogHeader>
<DialogTitle>Deployment</DialogTitle>
<DialogDescription>
See all the details of this deployment
<DialogDescription className="flex items-center gap-2">
<span>
See all the details of this deployment |{" "}
<Badge variant="blank" className="text-xs">
{filteredLogs.length} lines
</Badge>
</span>
{serverId && (
<div className="flex items-center space-x-2">
<Checkbox
id="show-extra-logs"
checked={showExtraLogs}
onCheckedChange={(checked) =>
setShowExtraLogs(checked as boolean)
}
/>
<label
htmlFor="show-extra-logs"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Show Extra Logs
</label>
</div>
)}
</DialogDescription>
</DialogHeader>
<div className="text-wrap rounded-lg border p-4 text-sm sm:max-w-[59rem]">
<code>
<pre className="whitespace-pre-wrap break-words">
{data || "Loading..."}
</pre>
<div ref={endOfLogsRef} />
</code>
<div
ref={scrollRef}
onScroll={handleScroll}
className="h-[720px] overflow-y-auto space-y-0 border p-4 bg-[#fafafa] dark:bg-[#050506] rounded custom-logs-scrollbar"
>
{filteredLogs.length > 0 ? (
filteredLogs.map((log: LogLine, index: number) => (
<TerminalLine key={index} log={log} noTimestamp />
))
) : (
<div className="flex justify-center items-center h-full text-muted-foreground">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
)}
</div>
</DialogContent>
</Dialog>
@@ -127,8 +127,8 @@ export const AddDomainCompose = ({
const dictionary = {
success: domainId ? "Domain Updated" : "Domain Created",
error: domainId
? "Error to update the domain"
: "Error to create the domain",
? "Error updating the domain"
: "Error creating the domain",
submit: domainId ? "Update" : "Create",
dialogDescription: domainId
? "In this section you can edit a domain"
@@ -400,21 +400,21 @@ export const AddDomainCompose = ({
name="certificateType"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Certificate</FormLabel>
<FormLabel>Certificate Provider</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a certificate" />
<SelectValue placeholder="Select a certificate provider" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem value={"letsencrypt"}>
Letsencrypt (Default)
Let's Encrypt
</SelectItem>
</SelectContent>
</Select>
@@ -70,7 +70,7 @@ export const ShowEnvironmentCompose = ({ composeId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to add environment");
toast.error("Error adding environment");
});
};
@@ -83,7 +83,7 @@ export const ComposeActions = ({ composeId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to update Auto Deploy");
toast.error("Error updating Auto Deploy");
});
}}
className="flex flex-row gap-2 items-center"
@@ -77,7 +77,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
});
})
.catch((e) => {
toast.error("Error to update the compose config");
toast.error("Error updating the Compose config");
});
};
return (
@@ -11,6 +11,7 @@ import {
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { useRouter } from "next/router";
import { toast } from "sonner";
interface Props {
@@ -18,6 +19,7 @@ interface Props {
}
export const DeployCompose = ({ composeId }: Props) => {
const router = useRouter();
const { data, refetch } = api.compose.one.useQuery(
{
composeId,
@@ -48,9 +50,15 @@ export const DeployCompose = ({ composeId }: Props) => {
await refetch();
await deploy({
composeId,
}).catch(() => {
toast.error("Error to deploy Compose");
});
})
.then(async () => {
router.push(
`/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`,
);
})
.catch(() => {
toast.error("Error deploying Compose");
});
await refetch();
}}
@@ -139,7 +139,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the Bitbucket provider");
toast.error("Error saving the Bitbucket provider");
});
};
@@ -83,7 +83,7 @@ export const SaveGitProviderCompose = ({ composeId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the Git provider");
toast.error("Error saving the Git provider");
});
};
@@ -133,7 +133,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the github provider");
toast.error("Error saving the Github provider");
});
};
@@ -146,7 +146,7 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the gitlab provider");
toast.error("Error saving the Gitlab provider");
});
};
@@ -90,7 +90,7 @@ export const RandomizeCompose = ({ composeId }: Props) => {
toast.success("Compose updated");
})
.catch(() => {
toast.error("Error to randomize the compose");
toast.error("Error randomizing the compose");
});
};
@@ -105,7 +105,7 @@ export const RandomizeCompose = ({ composeId }: Props) => {
toast.success("Compose randomized");
})
.catch(() => {
toast.error("Error to randomize the compose");
toast.error("Error randomizing the compose");
});
};
@@ -62,7 +62,7 @@ export const RedbuildCompose = ({ composeId }: Props) => {
});
})
.catch(() => {
toast.error("Error to rebuild the compose");
toast.error("Error rebuilding the compose");
});
}}
>
@@ -73,7 +73,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
toast.success("Fetched source type");
})
.catch((err) => {
toast.error("Error to fetch source type", {
toast.error("Error fetching source type", {
description: err.message,
});
});
@@ -53,10 +53,10 @@ export const StopCompose = ({ composeId }: Props) => {
await utils.compose.one.invalidate({
composeId,
});
toast.success("Compose stopped succesfully");
toast.success("Compose stopped successfully");
})
.catch(() => {
toast.error("Error to stop the compose");
toast.error("Error stopping the compose");
});
}}
>
@@ -1,161 +0,0 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { ImportIcon, SquarePen } from "lucide-react";
import { useEffect, useState } 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, {
message: "Name is required",
}),
description: z.string().optional(),
});
type UpdateCompose = z.infer<typeof updateComposeSchema>;
interface Props {
composeId: string;
}
export const ImportTemplate = ({ composeId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { mutateAsync, error, isError, isLoading } =
api.compose.update.useMutation();
const { data } = api.compose.one.useQuery(
{
composeId,
},
{
enabled: !!composeId,
},
);
const form = useForm<UpdateCompose>({
defaultValues: {
description: data?.description ?? "",
name: data?.name ?? "",
},
resolver: zodResolver(updateComposeSchema),
});
useEffect(() => {
if (data) {
form.reset({
description: data.description ?? "",
name: data.name,
});
}
}, [data, form, form.reset]);
const onSubmit = async (formData: UpdateCompose) => {
await mutateAsync({
name: formData.name,
composeId: composeId,
description: formData.description || "",
})
.then(() => {
toast.success("Compose updated succesfully");
utils.compose.one.invalidate({
composeId: composeId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the Compose");
})
.finally(() => {});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost">
<ImportIcon className="size-5 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Import Template</DialogTitle>
<DialogDescription>Import external template</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid gap-4">
<div className="grid items-center gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-update-compose"
className="grid w-full gap-4 "
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Tesla" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea
placeholder="Description about your project..."
className="resize-none"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button
isLoading={isLoading}
form="hook-form-update-compose"
type="submit"
>
Update
</Button>
</DialogFooter>
</form>
</Form>
</div>
</div>
</DialogContent>
</Dialog>
);
};
@@ -0,0 +1,165 @@
import { badgeStateColor } from "@/components/dashboard/application/logs/show";
import { Badge } from "@/components/ui/badge";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
export const DockerLogs = dynamic(
() =>
import("@/components/dashboard/docker/logs/docker-logs-id").then(
(e) => e.DockerLogsId,
),
{
ssr: false,
},
);
interface Props {
appName: string;
serverId?: string;
}
badgeStateColor;
export const ShowDockerLogsStack = ({ appName, serverId }: Props) => {
const [option, setOption] = useState<"swarm" | "native">("native");
const [containerId, setContainerId] = useState<string | undefined>();
const { data: services, isLoading: servicesLoading } =
api.docker.getStackContainersByAppName.useQuery(
{
appName,
serverId,
},
{
enabled: !!appName && option === "swarm",
},
);
const { data: containers, isLoading: containersLoading } =
api.docker.getContainersByAppNameMatch.useQuery(
{
appName,
appType: "stack",
serverId,
},
{
enabled: !!appName && option === "native",
},
);
useEffect(() => {
if (option === "native") {
if (containers && containers?.length > 0) {
setContainerId(containers[0]?.containerId);
}
} else {
if (services && services?.length > 0) {
setContainerId(services[0]?.containerId);
}
}
}, [option, services, containers]);
const isLoading = option === "native" ? containersLoading : servicesLoading;
const containersLenght =
option === "native" ? containers?.length : services?.length;
return (
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">Logs</CardTitle>
<CardDescription>
Watch the logs of the application in real time
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="flex flex-row justify-between items-center gap-2">
<Label>Select a container to view logs</Label>
<div className="flex flex-row gap-2 items-center">
<span className="text-sm text-muted-foreground">
{option === "native" ? "Native" : "Swarm"}
</span>
<Switch
checked={option === "native"}
onCheckedChange={(checked) => {
setOption(checked ? "native" : "swarm");
}}
/>
</div>
</div>
<Select onValueChange={setContainerId} value={containerId}>
<SelectTrigger>
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
) : (
<SelectValue placeholder="Select a container" />
)}
</SelectTrigger>
<SelectContent>
<SelectGroup>
{option === "native" ? (
<div>
{containers?.map((container) => (
<SelectItem
key={container.containerId}
value={container.containerId}
>
{container.name} ({container.containerId}){" "}
<Badge variant={badgeStateColor(container.state)}>
{container.state}
</Badge>
</SelectItem>
))}
</div>
) : (
<>
{services?.map((container) => (
<SelectItem
key={container.containerId}
value={container.containerId}
>
{container.name} ({container.containerId}@{container.node}
)
<Badge variant={badgeStateColor(container.state)}>
{container.state}
</Badge>
</SelectItem>
))}
</>
)}
<SelectLabel>Containers ({containersLenght})</SelectLabel>
</SelectGroup>
</SelectContent>
</Select>
<DockerLogs
serverId={serverId || ""}
containerId={containerId || "select-a-container"}
runType={option}
/>
</CardContent>
</Card>
);
};
@@ -1,3 +1,5 @@
import { badgeStateColor } from "@/components/dashboard/application/logs/show";
import { Badge } from "@/components/ui/badge";
import {
Card,
CardContent,
@@ -87,7 +89,10 @@ export const ShowDockerLogsCompose = ({
key={container.containerId}
value={container.containerId}
>
{container.name} ({container.containerId}) {container.state}
{container.name} ({container.containerId}){" "}
<Badge variant={badgeStateColor(container.state)}>
{container.state}
</Badge>
</SelectItem>
))}
<SelectLabel>Containers ({data?.length})</SelectLabel>
@@ -96,8 +101,8 @@ export const ShowDockerLogsCompose = ({
</Select>
<DockerLogs
serverId={serverId || ""}
id="terminal"
containerId={containerId || "select-a-container"}
runType="native"
/>
</CardContent>
</Card>
@@ -49,10 +49,10 @@ export const StartCompose = ({ composeId }: Props) => {
await utils.compose.one.invalidate({
composeId,
});
toast.success("Compose started succesfully");
toast.success("Compose started successfully");
})
.catch(() => {
toast.error("Error to start the Compose");
toast.error("Error starting the Compose");
});
}}
>
@@ -49,10 +49,10 @@ export const StopCompose = ({ composeId }: Props) => {
await utils.compose.one.invalidate({
composeId,
});
toast.success("Compose stopped succesfully");
toast.success("Compose stopped successfully");
})
.catch(() => {
toast.error("Error to stop the Compose");
toast.error("Error stopping the Compose");
});
}}
>
@@ -76,14 +76,14 @@ export const UpdateCompose = ({ composeId }: Props) => {
description: formData.description || "",
})
.then(() => {
toast.success("Compose updated succesfully");
toast.success("Compose updated successfully");
utils.compose.one.invalidate({
composeId: composeId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the Compose");
toast.error("Error updating the Compose");
})
.finally(() => {});
};
@@ -125,7 +125,7 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
refetch();
})
.catch(() => {
toast.error("Error to create a backup");
toast.error("Error creating a backup");
});
};
return (
@@ -46,10 +46,10 @@ export const DeleteBackup = ({ backupId, refetch }: Props) => {
.then(() => {
refetch();
toast.success("Backup delete succesfully");
toast.success("Backup deleted successfully");
})
.catch(() => {
toast.error("Error to delete the backup");
toast.error("Error deleting the backup");
});
}}
>
@@ -109,7 +109,7 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the backup");
toast.error("Error updating the Backup");
});
};
@@ -1,3 +1,4 @@
import { CodeEditor } from "@/components/shared/code-editor";
import {
Dialog,
DialogContent,
@@ -34,7 +35,7 @@ export const ShowContainerConfig = ({ containerId, serverId }: Props) => {
View Config
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className={"w-full md:w-[70vw] max-w-max"}>
<DialogContent className={"w-full md:w-[70vw] min-w-[70vw]"}>
<DialogHeader>
<DialogTitle>Container Config</DialogTitle>
<DialogDescription>
@@ -44,7 +45,13 @@ export const ShowContainerConfig = ({ containerId, serverId }: Props) => {
<div className="text-wrap rounded-lg border p-4 text-sm bg-card overflow-y-auto max-h-[80vh]">
<code>
<pre className="whitespace-pre-wrap break-words">
{JSON.stringify(data, null, 2)}
<CodeEditor
language="json"
lineWrapping
lineNumbers={false}
readOnly
value={JSON.stringify(data, null, 2)}
/>
</pre>
</code>
</div>
@@ -1,114 +1,296 @@
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Terminal } from "@xterm/xterm";
import { api } from "@/utils/api";
import { Download as DownloadIcon, Loader2 } from "lucide-react";
import React, { useEffect, useRef } from "react";
import { FitAddon } from "xterm-addon-fit";
import "@xterm/xterm/css/xterm.css";
import { LineCountFilter } from "./line-count-filter";
import { SinceLogsFilter, type TimeFilter } from "./since-logs-filter";
import { StatusLogsFilter } from "./status-logs-filter";
import { TerminalLine } from "./terminal-line";
import { type LogLine, getLogType, parseLogs } from "./utils";
interface Props {
id: string;
containerId: string;
serverId?: string | null;
runType: "swarm" | "native";
}
export const priorities = [
{
label: "Info",
value: "info",
},
{
label: "Success",
value: "success",
},
{
label: "Warning",
value: "warning",
},
{
label: "Debug",
value: "debug",
},
{
label: "Error",
value: "error",
},
];
export const DockerLogsId: React.FC<Props> = ({
id,
containerId,
serverId,
runType,
}) => {
const [term, setTerm] = React.useState<Terminal>();
const [lines, setLines] = React.useState<number>(40);
const wsRef = useRef<WebSocket | null>(null); // Ref to hold WebSocket instance
const { data } = api.docker.getConfig.useQuery(
{
containerId,
serverId: serverId ?? undefined,
},
{
enabled: !!containerId,
},
);
const [rawLogs, setRawLogs] = React.useState("");
const [filteredLogs, setFilteredLogs] = React.useState<LogLine[]>([]);
const [autoScroll, setAutoScroll] = React.useState(true);
const [lines, setLines] = React.useState<number>(100);
const [search, setSearch] = React.useState<string>("");
const [showTimestamp, setShowTimestamp] = React.useState(true);
const [since, setSince] = React.useState<TimeFilter>("all");
const [typeFilter, setTypeFilter] = React.useState<string[]>([]);
const scrollRef = useRef<HTMLDivElement>(null);
const [isLoading, setIsLoading] = React.useState(false);
const scrollToBottom = () => {
if (autoScroll && scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
};
const handleScroll = () => {
if (!scrollRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
setAutoScroll(isAtBottom);
};
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value || "");
};
const handleLines = (lines: number) => {
setRawLogs("");
setFilteredLogs([]);
setLines(lines);
};
const handleSince = (value: TimeFilter) => {
setRawLogs("");
setFilteredLogs([]);
setSince(value);
};
useEffect(() => {
// if (containerId === "select-a-container") {
// return;
// }
const container = document.getElementById(id);
if (container) {
container.innerHTML = "";
}
if (!containerId) return;
if (wsRef.current) {
if (wsRef.current.readyState === WebSocket.OPEN) {
wsRef.current.close();
}
wsRef.current = null;
}
const termi = new Terminal({
cursorBlink: true,
cols: 80,
rows: 30,
lineHeight: 1.25,
fontWeight: 400,
fontSize: 14,
fontFamily:
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
convertEol: true,
theme: {
cursor: "transparent",
background: "rgba(0, 0, 0, 0)",
},
});
let isCurrentConnection = true;
let noDataTimeout: NodeJS.Timeout;
setIsLoading(true);
setRawLogs("");
setFilteredLogs([]);
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const params = new globalThis.URLSearchParams({
containerId,
tail: lines.toString(),
since,
search,
runType,
});
const wsUrl = `${protocol}//${window.location.host}/docker-container-logs?containerId=${containerId}&tail=${lines}${serverId ? `&serverId=${serverId}` : ""}`;
if (serverId) {
params.append("serverId", serverId);
}
const wsUrl = `${protocol}//${
window.location.host
}/docker-container-logs?${params.toString()}`;
console.log("Connecting to WebSocket:", wsUrl);
const ws = new WebSocket(wsUrl);
wsRef.current = ws;
const fitAddon = new FitAddon();
termi.loadAddon(fitAddon);
// @ts-ignore
termi.open(container);
fitAddon.fit();
termi.focus();
setTerm(termi);
ws.onerror = (error) => {
console.error("WebSocket error: ", error);
const resetNoDataTimeout = () => {
if (noDataTimeout) clearTimeout(noDataTimeout);
noDataTimeout = setTimeout(() => {
if (isCurrentConnection) {
setIsLoading(false);
}
}, 2000); // Wait 2 seconds for data before showing "No logs found"
};
ws.onopen = () => {
if (!isCurrentConnection) {
ws.close();
return;
}
console.log("WebSocket connected");
resetNoDataTimeout();
};
ws.onmessage = (e) => {
termi.write(e.data);
if (!isCurrentConnection) return;
setRawLogs((prev) => prev + e.data);
setIsLoading(false);
if (noDataTimeout) clearTimeout(noDataTimeout);
};
ws.onerror = (error) => {
if (!isCurrentConnection) return;
console.error("WebSocket error:", error);
setIsLoading(false);
if (noDataTimeout) clearTimeout(noDataTimeout);
};
ws.onclose = (e) => {
console.log(e.reason);
termi.write(`Connection closed!\nReason: ${e.reason}\n`);
wsRef.current = null;
if (!isCurrentConnection) return;
console.log("WebSocket closed:", e.reason);
setIsLoading(false);
if (noDataTimeout) clearTimeout(noDataTimeout);
};
return () => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
isCurrentConnection = false;
if (noDataTimeout) clearTimeout(noDataTimeout);
if (ws.readyState === WebSocket.OPEN) {
ws.close();
wsRef.current = null;
}
};
}, [lines, containerId]);
}, [containerId, serverId, lines, search, since]);
const handleDownload = () => {
const logContent = filteredLogs
.map(
({ timestamp, message }: { timestamp: Date | null; message: string }) =>
`${timestamp?.toISOString() || "No timestamp"} ${message}`,
)
.join("\n");
const blob = new Blob([logContent], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
const appName = data.Name.replace("/", "") || "app";
const isoDate = new Date().toISOString();
a.href = url;
a.download = `${appName}-${isoDate.slice(0, 10).replace(/-/g, "")}_${isoDate
.slice(11, 19)
.replace(/:/g, "")}.log.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
const handleFilter = (logs: LogLine[]) => {
return logs.filter((log) => {
const logType = getLogType(log.message).type;
if (typeFilter.length === 0) {
return true;
}
return typeFilter.includes(logType);
});
};
useEffect(() => {
term?.clear();
}, [lines, term]);
setRawLogs("");
setFilteredLogs([]);
}, [containerId]);
useEffect(() => {
const logs = parseLogs(rawLogs);
const filtered = handleFilter(logs);
setFilteredLogs(filtered);
}, [rawLogs, search, lines, since, typeFilter]);
useEffect(() => {
scrollToBottom();
if (autoScroll && scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [filteredLogs, autoScroll]);
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<Label>
<span>Number of lines to show</span>
</Label>
<Input
type="text"
placeholder="Number of lines to show (Defaults to 35)"
value={lines}
onChange={(e) => {
setLines(Number(e.target.value) || 1);
}}
/>
</div>
<div className="rounded-lg overflow-hidden">
<div className="space-y-4">
<div className="flex flex-wrap justify-between items-start sm:items-center gap-4">
<div className="flex flex-wrap gap-4">
<LineCountFilter value={lines} onValueChange={handleLines} />
<div className="w-full h-full rounded-lg p-2 bg-[#19191A]">
<div id={id} />
<SinceLogsFilter
value={since}
onValueChange={handleSince}
showTimestamp={showTimestamp}
onTimestampChange={setShowTimestamp}
/>
<StatusLogsFilter
value={typeFilter}
setValue={setTypeFilter}
title="Log type"
options={priorities}
/>
<Input
type="search"
placeholder="Search logs..."
value={search}
onChange={handleSearch}
className="inline-flex h-9 text-sm placeholder-gray-400 w-full sm:w-auto"
/>
</div>
<Button
variant="outline"
size="sm"
className="h-9 sm:w-auto w-full"
onClick={handleDownload}
disabled={filteredLogs.length === 0 || !data?.Name}
>
<DownloadIcon className="mr-2 h-4 w-4" />
Download logs
</Button>
</div>
<div
ref={scrollRef}
onScroll={handleScroll}
className="h-[720px] overflow-y-auto space-y-0 border p-4 bg-[#fafafa] dark:bg-[#050506] rounded custom-logs-scrollbar"
>
{filteredLogs.length > 0 ? (
filteredLogs.map((filteredLog: LogLine, index: number) => (
<TerminalLine
key={index}
log={filteredLog}
searchTerm={search}
noTimestamp={!showTimestamp}
/>
))
) : isLoading ? (
<div className="flex justify-center items-center h-full text-muted-foreground">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
) : (
<div className="flex justify-center items-center h-full text-muted-foreground">
No logs found
</div>
)}
</div>
</div>
</div>
</div>
);
@@ -0,0 +1,173 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
import { Command as CommandPrimitive } from "cmdk";
import { debounce } from "lodash";
import { CheckIcon, Hash } from "lucide-react";
import React, { useCallback, useRef } from "react";
const lineCountOptions = [
{ label: "100 lines", value: 100 },
{ label: "300 lines", value: 300 },
{ label: "500 lines", value: 500 },
{ label: "1000 lines", value: 1000 },
{ label: "5000 lines", value: 5000 },
] as const;
interface LineCountFilterProps {
value: number;
onValueChange: (value: number) => void;
title?: string;
}
export function LineCountFilter({
value,
onValueChange,
title = "Limit to",
}: LineCountFilterProps) {
const [open, setOpen] = React.useState(false);
const [inputValue, setInputValue] = React.useState("");
const pendingValueRef = useRef<number | null>(null);
const isPresetValue = lineCountOptions.some(
(option) => option.value === value,
);
const debouncedValueChange = useCallback(
debounce((numValue: number) => {
if (numValue > 0 && numValue !== value) {
onValueChange(numValue);
pendingValueRef.current = null;
}
}, 500),
[onValueChange, value],
);
const handleInputChange = (input: string) => {
setInputValue(input);
// Extract numbers from input and convert
const numValue = Number.parseInt(input.replace(/[^0-9]/g, ""));
if (!Number.isNaN(numValue)) {
pendingValueRef.current = numValue;
debouncedValueChange(numValue);
}
};
const handleSelect = (selectedValue: string) => {
const preset = lineCountOptions.find((opt) => opt.label === selectedValue);
if (preset) {
if (preset.value !== value) {
onValueChange(preset.value);
}
setInputValue("");
setOpen(false);
return;
}
const numValue = Number.parseInt(selectedValue);
if (
!Number.isNaN(numValue) &&
numValue > 0 &&
numValue !== value &&
numValue !== pendingValueRef.current
) {
onValueChange(numValue);
setInputValue("");
setOpen(false);
}
};
React.useEffect(() => {
return () => {
debouncedValueChange.cancel();
};
}, [debouncedValueChange]);
const displayValue = isPresetValue
? lineCountOptions.find((option) => option.value === value)?.label
: `${value} lines`;
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
className="h-9 bg-input text-sm placeholder-gray-400 w-full sm:w-auto"
>
{title}
<Separator orientation="vertical" className="mx-2 h-4" />
<div className="space-x-1 flex">
<Badge variant="blank" className="rounded-sm px-1 font-normal">
{displayValue}
</Badge>
</div>
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0" align="start">
<CommandPrimitive className="overflow-hidden rounded-md border border-none bg-popover text-popover-foreground">
<div className="flex items-center border-b px-3">
<Hash className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
placeholder="Number of lines"
value={inputValue}
onValueChange={handleInputChange}
className="flex h-9 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
const numValue = Number.parseInt(
inputValue.replace(/[^0-9]/g, ""),
);
if (
!Number.isNaN(numValue) &&
numValue > 0 &&
numValue !== value &&
numValue !== pendingValueRef.current
) {
handleSelect(inputValue);
}
}
}}
/>
</div>
<CommandPrimitive.List className="max-h-[300px] overflow-y-auto overflow-x-hidden">
<CommandPrimitive.Group className="px-2 py-1.5">
{lineCountOptions.map((option) => {
const isSelected = value === option.value;
return (
<CommandPrimitive.Item
key={option.value}
onSelect={() => handleSelect(option.label)}
className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 aria-selected:bg-accent aria-selected:text-accent-foreground"
>
<div
className={cn(
"flex h-4 w-4 items-center justify-center rounded-sm border border-primary mr-2",
isSelected
? "bg-primary text-primary-foreground"
: "opacity-50 [&_svg]:invisible",
)}
>
<CheckIcon className={cn("h-4 w-4")} />
</div>
<span>{option.label}</span>
</CommandPrimitive.Item>
);
})}
</CommandPrimitive.Group>
</CommandPrimitive.List>
</CommandPrimitive>
</PopoverContent>
</Popover>
);
}
export default LineCountFilter;
@@ -47,9 +47,9 @@ export const ShowDockerModalLogs = ({
</DialogHeader>
<div className="flex flex-col gap-4 pt-2.5">
<DockerLogsId
id="terminal"
containerId={containerId || ""}
serverId={serverId}
runType="native"
/>
</div>
</DialogContent>
@@ -0,0 +1,58 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
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(
(e) => e.DockerLogsId,
),
{
ssr: false,
},
);
interface Props {
containerId: string;
children?: React.ReactNode;
serverId?: string | null;
}
export const ShowDockerModalStackLogs = ({
containerId,
children,
serverId,
}: Props) => {
return (
<Dialog>
<DialogTrigger asChild>
<DropdownMenuItem
className="w-full cursor-pointer space-x-3"
onSelect={(e) => e.preventDefault()}
>
{children}
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl">
<DialogHeader>
<DialogTitle>View Logs</DialogTitle>
<DialogDescription>View the logs for {containerId}</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4 pt-2.5">
<DockerLogsId
containerId={containerId || ""}
serverId={serverId}
runType="swarm"
/>
</div>
</DialogContent>
</Dialog>
);
};
@@ -0,0 +1,125 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandGroup,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils";
import { CheckIcon } from "lucide-react";
import React from "react";
export type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h";
const timeRanges: Array<{ label: string; value: TimeFilter }> = [
{
label: "All time",
value: "all",
},
{
label: "Last hour",
value: "1h",
},
{
label: "Last 6 hours",
value: "6h",
},
{
label: "Last 24 hours",
value: "24h",
},
{
label: "Last 7 days",
value: "168h",
},
{
label: "Last 30 days",
value: "720h",
},
] as const;
interface SinceLogsFilterProps {
value: TimeFilter;
onValueChange: (value: TimeFilter) => void;
showTimestamp: boolean;
onTimestampChange: (show: boolean) => void;
title?: string;
}
export function SinceLogsFilter({
value,
onValueChange,
showTimestamp,
onTimestampChange,
title = "Time range",
}: SinceLogsFilterProps) {
const selectedLabel =
timeRanges.find((range) => range.value === value)?.label ??
"Select time range";
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
className="h-9 bg-input text-sm placeholder-gray-400 w-full sm:w-auto"
>
{title}
<Separator orientation="vertical" className="mx-2 h-4" />
<div className="space-x-1 flex">
<Badge variant="blank" className="rounded-sm px-1 font-normal">
{selectedLabel}
</Badge>
</div>
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0" align="start">
<Command>
<CommandList>
<CommandGroup>
{timeRanges.map((range) => {
const isSelected = value === range.value;
return (
<CommandItem
key={range.value}
onSelect={() => {
if (!isSelected) {
onValueChange(range.value);
}
}}
>
<div
className={cn(
"mr-2 flex h-4 w-4 items-center rounded-sm border border-primary",
isSelected
? "bg-primary text-primary-foreground"
: "opacity-50 [&_svg]:invisible",
)}
>
<CheckIcon className={cn("h-4 w-4")} />
</div>
<span className="text-sm">{range.label}</span>
</CommandItem>
);
})}
</CommandGroup>
</CommandList>
</Command>
<Separator className="my-2" />
<div className="p-2 flex items-center justify-between">
<span className="text-sm">Show timestamps</span>
<Switch checked={showTimestamp} onCheckedChange={onTimestampChange} />
</div>
</PopoverContent>
</Popover>
);
}
@@ -0,0 +1,170 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandGroup,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
import { CheckIcon } from "lucide-react";
import type React from "react";
interface StatusLogsFilterProps {
value?: string[];
setValue?: (value: string[]) => void;
title?: string;
options: {
label: string;
value: string;
icon?: React.ComponentType<{ className?: string }>;
}[];
}
export function StatusLogsFilter({
value = [],
setValue,
title,
options,
}: StatusLogsFilterProps) {
const selectedValues = new Set(value as string[]);
const allSelected = selectedValues.size === 0;
const getSelectedBadges = () => {
if (allSelected) {
return (
<Badge variant="blank" className="rounded-sm px-1 font-normal">
All
</Badge>
);
}
if (selectedValues.size >= 1) {
const selected = options.find((opt) => selectedValues.has(opt.value));
return (
<>
<Badge
variant={
selected?.value === "success"
? "green"
: selected?.value === "error"
? "red"
: selected?.value === "warning"
? "orange"
: selected?.value === "info"
? "blue"
: selected?.value === "debug"
? "yellow"
: "blank"
}
className="rounded-sm px-1 font-normal"
>
{selected?.label}
</Badge>
{selectedValues.size > 1 && (
<Badge variant="blank" className="rounded-sm px-1 font-normal">
+{selectedValues.size - 1}
</Badge>
)}
</>
);
}
return null;
};
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
className="h-9 bg-input text-sm placeholder-gray-400 w-full sm:w-auto"
>
{title}
<Separator orientation="vertical" className="mx-2 h-4" />
<div className="space-x-1 flex">{getSelectedBadges()}</div>
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0" align="start">
<Command>
<CommandList>
<CommandGroup>
<CommandItem
onSelect={() => {
setValue?.([]); // Empty array means "All"
}}
>
<div
className={cn(
"mr-2 flex h-4 w-4 items-center rounded-sm border border-primary",
allSelected
? "bg-primary text-primary-foreground"
: "opacity-50 [&_svg]:invisible",
)}
>
<CheckIcon className={cn("h-4 w-4")} />
</div>
<Badge variant="blank">All</Badge>
</CommandItem>
{options.map((option) => {
const isSelected = selectedValues.has(option.value);
return (
<CommandItem
key={option.value}
onSelect={() => {
const newValues = new Set(selectedValues);
if (isSelected) {
newValues.delete(option.value);
} else {
newValues.add(option.value);
}
setValue?.(Array.from(newValues));
}}
>
<div
className={cn(
"mr-2 flex h-4 w-4 items-center rounded-sm border border-primary",
isSelected
? "bg-primary text-primary-foreground"
: "opacity-50 [&_svg]:invisible",
)}
>
<CheckIcon className={cn("h-4 w-4")} />
</div>
{option.icon && (
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
)}
<Badge
variant={
option.value === "success"
? "green"
: option.value === "error"
? "red"
: option.value === "warning"
? "orange"
: option.value === "info"
? "blue"
: option.value === "debug"
? "yellow"
: "blank"
}
>
{option.label}
</Badge>
</CommandItem>
);
})}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
@@ -0,0 +1,139 @@
import { Badge } from "@/components/ui/badge";
import {
Tooltip,
TooltipContent,
TooltipPortal,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { FancyAnsi } from "fancy-ansi";
import { escapeRegExp } from "lodash";
import React from "react";
import { type LogLine, getLogType } from "./utils";
interface LogLineProps {
log: LogLine;
noTimestamp?: boolean;
searchTerm?: string;
}
const fancyAnsi = new FancyAnsi();
export function TerminalLine({ log, noTimestamp, searchTerm }: LogLineProps) {
const { timestamp, message, rawTimestamp } = log;
const { type, variant, color } = getLogType(message);
const formattedTime = timestamp
? timestamp.toLocaleString([], {
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
year: "2-digit",
second: "2-digit",
})
: "--- No time found ---";
const highlightMessage = (text: string, term: string) => {
if (!term) {
return (
<span
className="transition-colors"
dangerouslySetInnerHTML={{
__html: fancyAnsi.toHtml(text),
}}
/>
);
}
const htmlContent = fancyAnsi.toHtml(text);
const modifiedContent = htmlContent.replace(
/<span([^>]*)>([^<]*)<\/span>/g,
(match, attrs, content) => {
const searchRegex = new RegExp(`(${escapeRegExp(term)})`, "gi");
if (!content.match(searchRegex)) return match;
const segments = content.split(searchRegex);
const wrappedSegments = segments
.map((segment: string) =>
segment.toLowerCase() === term.toLowerCase()
? `<span${attrs} class="bg-yellow-200/50 dark:bg-yellow-900/50">${segment}</span>`
: segment,
)
.join("");
return `<span${attrs}>${wrappedSegments}</span>`;
},
);
return (
<span
className="transition-colors"
dangerouslySetInnerHTML={{ __html: modifiedContent }}
/>
);
};
const tooltip = (color: string, timestamp: string | null) => {
const square = (
<div className={cn("w-2 h-full flex-shrink-0 rounded-[3px]", color)} />
);
return timestamp ? (
<TooltipProvider delayDuration={0} disableHoverableContent>
<Tooltip>
<TooltipTrigger asChild>{square}</TooltipTrigger>
<TooltipPortal>
<TooltipContent
sideOffset={5}
className="bg-popover border-border z-[99999]"
>
<p className="text text-xs text-muted-foreground break-all max-w-md">
<pre>{timestamp}</pre>
</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>
</TooltipProvider>
) : (
square
);
};
return (
<div
className={cn(
"font-mono text-xs flex flex-row gap-3 py-2 sm:py-0.5 group",
type === "error"
? "bg-red-500/10 hover:bg-red-500/15"
: type === "warning"
? "bg-yellow-500/10 hover:bg-yellow-500/15"
: type === "debug"
? "bg-orange-500/10 hover:bg-orange-500/15"
: "hover:bg-gray-200/50 dark:hover:bg-gray-800/50",
)}
>
{" "}
<div className="flex items-start gap-x-2">
{/* Icon to expand the log item maybe implement a colapsible later */}
{/* <Square className="size-4 text-muted-foreground opacity-0 group-hover/logitem:opacity-100 transition-opacity" /> */}
{tooltip(color, rawTimestamp)}
{!noTimestamp && (
<span className="select-none pl-2 text-muted-foreground w-full sm:w-40 flex-shrink-0">
{formattedTime}
</span>
)}
<Badge
variant={variant}
className="w-14 justify-center text-[10px] px-1 py-0"
>
{type}
</Badge>
</div>
<span className="dark:text-gray-200 font-mono text-foreground whitespace-pre-wrap break-all">
{highlightMessage(message, searchTerm || "")}
</span>
</div>
);
}
@@ -0,0 +1,152 @@
export type LogType = "error" | "warning" | "success" | "info" | "debug";
export type LogVariant = "red" | "yellow" | "green" | "blue" | "orange";
export interface LogLine {
rawTimestamp: string | null;
timestamp: Date | null;
message: string;
}
interface LogStyle {
type: LogType;
variant: LogVariant;
color: string;
}
const LOG_STYLES: Record<LogType, LogStyle> = {
error: {
type: "error",
variant: "red",
color: "bg-red-500/40",
},
warning: {
type: "warning",
variant: "orange",
color: "bg-orange-500/40",
},
debug: {
type: "debug",
variant: "yellow",
color: "bg-yellow-500/40",
},
success: {
type: "success",
variant: "green",
color: "bg-green-500/40",
},
info: {
type: "info",
variant: "blue",
color: "bg-blue-600/40",
},
} as const;
export function parseLogs(logString: string): LogLine[] {
// Regex to match the log line format
// Exemple of return :
// 1 2024-12-10T10:00:00.000Z The server is running on port 8080
// Should return :
// { timestamp: new Date("2024-12-10T10:00:00.000Z"),
// message: "The server is running on port 8080" }
const logRegex =
/^(?:(\d+)\s+)?(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z|\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} UTC)?\s*(.*)$/;
return logString
.split("\n")
.map((line) => line.trim())
.filter((line) => line !== "")
.map((line) => {
const match = line.match(logRegex);
if (!match) return null;
const [, , timestamp, message] = match;
if (!message?.trim()) return null;
// Delete other timestamps and keep only the one from --timestamps
const cleanedMessage = message
?.replace(
/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z|\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} UTC/g,
"",
)
.trim();
return {
rawTimestamp: timestamp ?? null,
timestamp: timestamp ? new Date(timestamp.replace(" UTC", "Z")) : null,
message: cleanedMessage,
};
})
.filter((log) => log !== null);
}
// Detect log type based on message content
export const getLogType = (message: string): LogStyle => {
const lowerMessage = message.toLowerCase();
if (
/(?:^|\s)(?:info|inf|information):?\s/i.test(lowerMessage) ||
/\[(?:info|information)\]/i.test(lowerMessage) ||
/\b(?:status|state|current|progress)\b:?\s/i.test(lowerMessage) ||
/\b(?:processing|executing|performing)\b/i.test(lowerMessage)
) {
return LOG_STYLES.info;
}
if (
/(?:^|\s)(?:error|err):?\s/i.test(lowerMessage) ||
/\b(?:exception|failed|failure)\b/i.test(lowerMessage) ||
/(?:stack\s?trace):\s*$/i.test(lowerMessage) ||
/^\s*at\s+[\w.]+\s*\(?.+:\d+:\d+\)?/.test(lowerMessage) ||
/\b(?:uncaught|unhandled)\s+(?:exception|error)\b/i.test(lowerMessage) ||
/Error:\s.*(?:in|at)\s+.*:\d+(?::\d+)?/.test(lowerMessage) ||
/\b(?:errno|code):\s*(?:\d+|[A-Z_]+)\b/i.test(lowerMessage) ||
/\[(?:error|err|fatal)\]/i.test(lowerMessage) ||
/\b(?:crash|critical|fatal)\b/i.test(lowerMessage) ||
/\b(?:fail(?:ed|ure)?|broken|dead)\b/i.test(lowerMessage)
) {
return LOG_STYLES.error;
}
if (
/(?:^|\s)(?:warning|warn):?\s/i.test(lowerMessage) ||
/\[(?:warn(?:ing)?|attention)\]/i.test(lowerMessage) ||
/(?:deprecated|obsolete)\s+(?:since|in|as\s+of)/i.test(lowerMessage) ||
/\b(?:caution|attention|notice):\s/i.test(lowerMessage) ||
/(?:might|may|could)\s+(?:not|cause|lead\s+to)/i.test(lowerMessage) ||
/(?:!+\s*(?:warning|caution|attention)\s*!+)/i.test(lowerMessage) ||
/\b(?:deprecated|obsolete)\b/i.test(lowerMessage) ||
/\b(?:unstable|experimental)\b/i.test(lowerMessage)
) {
return LOG_STYLES.warning;
}
if (
/(?:successfully|complete[d]?)\s+(?:initialized|started|completed|created|done|deployed)/i.test(
lowerMessage,
) ||
/\[(?:success|ok|done)\]/i.test(lowerMessage) ||
/(?:listening|running)\s+(?:on|at)\s+(?:port\s+)?\d+/i.test(lowerMessage) ||
/(?:connected|established|ready)\s+(?:to|for|on)/i.test(lowerMessage) ||
/\b(?:loaded|mounted|initialized)\s+successfully\b/i.test(lowerMessage) ||
/✓|√|✅|\[ok\]|done!/i.test(lowerMessage) ||
/\b(?:success(?:ful)?|completed|ready)\b/i.test(lowerMessage) ||
/\b(?:started|starting|active)\b/i.test(lowerMessage)
) {
return LOG_STYLES.success;
}
if (
/(?:^|\s)(?:info|inf):?\s/i.test(lowerMessage) ||
/\[(info|log|debug|trace|server|db|api|http|request|response)\]/i.test(
lowerMessage,
) ||
/\b(?:version|config|import|load|get|HTTP|PATCH|POST|debug)\b:?/i.test(
lowerMessage,
)
) {
return LOG_STYLES.debug;
}
return LOG_STYLES.info;
};
@@ -59,7 +59,10 @@ export const DockerTerminalModal = ({
{children}
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl">
<DialogContent
className="max-h-screen overflow-y-auto sm:max-w-7xl"
onEscapeKeyDown={(event) => event.preventDefault()}
>
<DialogHeader>
<DialogTitle>Docker Terminal</DialogTitle>
<DialogDescription>
@@ -73,7 +76,7 @@ export const DockerTerminalModal = ({
serverId={serverId || ""}
/>
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
<DialogContent>
<DialogContent onEscapeKeyDown={(event) => event.preventDefault()}>
<DialogHeader>
<DialogTitle>
Are you sure you want to close the terminal?
@@ -4,6 +4,7 @@ import { FitAddon } from "xterm-addon-fit";
import "@xterm/xterm/css/xterm.css";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { AttachAddon } from "@xterm/addon-attach";
import { useTheme } from "next-themes";
interface Props {
id: string;
@@ -18,6 +19,7 @@ export const DockerTerminal: React.FC<Props> = ({
}) => {
const termRef = useRef(null);
const [activeWay, setActiveWay] = React.useState<string | undefined>("bash");
const { resolvedTheme } = useTheme();
useEffect(() => {
const container = document.getElementById(id);
if (container) {
@@ -25,13 +27,12 @@ export const DockerTerminal: React.FC<Props> = ({
}
const term = new Terminal({
cursorBlink: true,
cols: 80,
rows: 30,
lineHeight: 1.4,
convertEol: true,
theme: {
cursor: "transparent",
cursor: resolvedTheme === "light" ? "#000000" : "transparent",
background: "rgba(0, 0, 0, 0)",
foreground: "currentColor",
},
});
const addonFit = new FitAddon();
@@ -45,6 +46,7 @@ export const DockerTerminal: React.FC<Props> = ({
const addonAttach = new AttachAddon(ws);
// @ts-ignore
term.open(termRef.current);
// @ts-ignore
term.loadAddon(addonFit);
term.loadAddon(addonAttach);
addonFit.fit();
@@ -66,7 +68,7 @@ export const DockerTerminal: React.FC<Props> = ({
</TabsList>
</Tabs>
</div>
<div className="w-full h-full rounded-lg p-2 bg-[#19191A]">
<div className="w-full h-full rounded-lg p-2 bg-transparent border">
<div id={id} ref={termRef} />
</div>
</div>
@@ -86,7 +86,7 @@ export const ShowTraefikFile = ({ path, serverId }: Props) => {
refetch();
})
.catch(() => {
toast.error("Error to update the traefik config");
toast.error("Error updating the Traefik config");
});
};
@@ -65,7 +65,7 @@ export const ShowAdvancedMariadb = ({ mariadbId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to Update the resources");
toast.error("Error updating the resources");
});
};
return (
@@ -18,6 +18,13 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import {
TooltipProvider,
TooltipTrigger,
TooltipContent,
Tooltip,
} from "@/components/ui/tooltip";
import { InfoIcon } from "lucide-react";
import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -71,7 +78,7 @@ export const ShowMariadbResources = ({ mariadbId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to Update the resources");
toast.error("Error updating the resources");
});
};
return (
@@ -100,28 +107,40 @@ export const ShowMariadbResources = ({ mariadbId }: Props) => {
name="memoryReservation"
render={({ field }) => (
<FormItem>
<FormLabel>Memory Reservation</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>Memory Reservation</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
Memory soft limit in bytes. Example: 256MB =
268435456 bytes
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder="256 MB"
placeholder="268435456 (256MB in bytes)"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
// Si el campo está vacío, establece el valor como null.
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
// Solo actualiza el valor si se convierte a un número válido.
field.onChange(number);
}
}
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
@@ -133,21 +152,34 @@ export const ShowMariadbResources = ({ mariadbId }: Props) => {
render={({ field }) => {
return (
<FormItem>
<FormLabel>Memory Limit</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>Memory Limit</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
Memory hard limit in bytes. Example: 1GB =
1073741824 bytes
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder={"1024 MB"}
placeholder="1073741824 (1GB in bytes)"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
// Si el campo está vacío, establece el valor como null.
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
// Solo actualiza el valor si se convierte a un número válido.
field.onChange(number);
}
}
@@ -166,21 +198,34 @@ export const ShowMariadbResources = ({ mariadbId }: Props) => {
render={({ field }) => {
return (
<FormItem>
<FormLabel>Cpu Limit</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>CPU Limit</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
CPU quota in units of 10^-9 CPUs. Example: 2
CPUs = 2000000000
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder={"2"}
placeholder="2000000000 (2 CPUs)"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
// Si el campo está vacío, establece el valor como null.
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
// Solo actualiza el valor si se convierte a un número válido.
field.onChange(number);
}
}
@@ -198,21 +243,34 @@ export const ShowMariadbResources = ({ mariadbId }: Props) => {
render={({ field }) => {
return (
<FormItem>
<FormLabel>Cpu Reservation</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>CPU Reservation</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
CPU shares (relative weight). Example: 1 CPU =
1000000000
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder={"1"}
placeholder="1000000000 (1 CPU)"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
// Si el campo está vacío, establece el valor como null.
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
// Solo actualiza el valor si se convierte a un número válido.
field.onChange(number);
}
}
@@ -144,7 +144,7 @@ export const ShowBackupMariadb = ({ mariadbId }: Props) => {
})
.catch(() => {
toast.error(
"Error to Create the manual backup",
"Error creating the manual backup",
);
});
}}
@@ -1,3 +1,4 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -19,7 +20,7 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { Copy, TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
@@ -99,9 +100,26 @@ export const DeleteMariadb = ({ mariadbId }: Props) => {
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
<FormLabel className="flex items-center gap-2">
<span>
To confirm, type{" "}
<Badge
className="p-2 rounded-md ml-1 mr-1 hover:border-primary hover:text-primary-foreground hover:bg-primary hover:cursor-pointer"
variant="outline"
onClick={() => {
if (data?.name && data?.appName) {
navigator.clipboard.writeText(
`${data.name}/${data.appName}`,
);
toast.success("Copied to clipboard. Be careful!");
}
}}
>
{data?.name}/{data?.appName}&nbsp;
<Copy className="h-4 w-4 ml-1 text-muted-foreground" />
</Badge>{" "}
in the box below:
</span>
</FormLabel>
<FormControl>
<Input
@@ -70,7 +70,7 @@ export const ShowMariadbEnvironment = ({ mariadbId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to add environment");
toast.error("Error adding environment");
});
};
@@ -55,12 +55,12 @@ export const DeployMariadb = ({ mariadbId }: Props) => {
await deploy({
mariadbId,
}).catch(() => {
toast.error("Error to deploy Database");
toast.error("Error deploying Database");
});
await refetch();
})
.catch((e) => {
toast.error(e.message || "Error to deploy Database");
toast.error(e.message || "Error deploying Database");
});
}}
>
@@ -55,7 +55,7 @@ export const ResetMariadb = ({ mariadbId, appName }: Props) => {
toast.success("Service Reloaded");
})
.catch(() => {
toast.error("Error to reload the service");
toast.error("Error reloading the service");
});
await refetch();
}}
@@ -72,7 +72,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the external port");
toast.error("Error saving the external port");
});
};
@@ -49,10 +49,10 @@ export const StopMariadb = ({ mariadbId }: Props) => {
await utils.mariadb.one.invalidate({
mariadbId,
});
toast.success("Application stopped succesfully");
toast.success("Application stopped successfully");
})
.catch(() => {
toast.error("Error to stop the Application");
toast.error("Error stopping the Application");
});
}}
>
@@ -49,10 +49,10 @@ export const StartMariadb = ({ mariadbId }: Props) => {
await utils.mariadb.one.invalidate({
mariadbId,
});
toast.success("Database started succesfully");
toast.success("Database started successfully");
})
.catch(() => {
toast.error("Error to start the Database");
toast.error("Error starting the Database");
});
}}
>
@@ -75,13 +75,13 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
description: formData.description || "",
})
.then(() => {
toast.success("MariaDB updated succesfully");
toast.success("MariaDB updated successfully");
utils.mariadb.one.invalidate({
mariadbId: mariadbId,
});
})
.catch(() => {
toast.error("Error to update the Mariadb");
toast.error("Error updating the Mariadb");
})
.finally(() => {});
};
@@ -65,7 +65,7 @@ export const ShowAdvancedMongo = ({ mongoId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to Update the resources");
toast.error("Error updating the resources");
});
};
return (
@@ -19,6 +19,13 @@ import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import React, { useEffect } from "react";
import {
TooltipProvider,
TooltipTrigger,
TooltipContent,
Tooltip,
} from "@/components/ui/tooltip";
import { InfoIcon } from "lucide-react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
@@ -71,7 +78,7 @@ export const ShowMongoResources = ({ mongoId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to Update the resources");
toast.error("Error updating the resources");
});
};
return (
@@ -89,10 +96,6 @@ export const ShowMongoResources = ({ mongoId }: Props) => {
the changes.
</AlertBlock>
<Form {...form}>
<AlertBlock type="info">
Please remember to click Redeploy after modify the resources to
apply the changes.
</AlertBlock>
<form
id="hook-form"
onSubmit={form.handleSubmit(onSubmit)}
@@ -104,28 +107,40 @@ export const ShowMongoResources = ({ mongoId }: Props) => {
name="memoryReservation"
render={({ field }) => (
<FormItem>
<FormLabel>Memory Reservation</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>Memory Reservation</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
Memory soft limit in bytes. Example: 256MB =
268435456 bytes
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder="256 MB"
placeholder="268435456 (256MB in bytes)"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
// Si el campo está vacío, establece el valor como null.
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
// Solo actualiza el valor si se convierte a un número válido.
field.onChange(number);
}
}
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
@@ -137,21 +152,34 @@ export const ShowMongoResources = ({ mongoId }: Props) => {
render={({ field }) => {
return (
<FormItem>
<FormLabel>Memory Limit</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>Memory Limit</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
Memory hard limit in bytes. Example: 1GB =
1073741824 bytes
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder={"1024 MB"}
placeholder="1073741824 (1GB in bytes)"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
// Si el campo está vacío, establece el valor como null.
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
// Solo actualiza el valor si se convierte a un número válido.
field.onChange(number);
}
}
@@ -170,21 +198,34 @@ export const ShowMongoResources = ({ mongoId }: Props) => {
render={({ field }) => {
return (
<FormItem>
<FormLabel>Cpu Limit</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>CPU Limit</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
CPU quota in units of 10^-9 CPUs. Example: 2
CPUs = 2000000000
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder={"2"}
placeholder="2000000000 (2 CPUs)"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
// Si el campo está vacío, establece el valor como null.
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
// Solo actualiza el valor si se convierte a un número válido.
field.onChange(number);
}
}
@@ -202,21 +243,34 @@ export const ShowMongoResources = ({ mongoId }: Props) => {
render={({ field }) => {
return (
<FormItem>
<FormLabel>Cpu Reservation</FormLabel>
<div className="flex items-center gap-2">
<FormLabel>CPU Reservation</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
CPU shares (relative weight). Example: 1 CPU =
1000000000
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder={"1"}
placeholder="1000000000 (1 CPU)"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
// Si el campo está vacío, establece el valor como null.
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
// Solo actualiza el valor si se convierte a un número válido.
field.onChange(number);
}
}
@@ -144,7 +144,7 @@ export const ShowBackupMongo = ({ mongoId }: Props) => {
})
.catch(() => {
toast.error(
"Error to Create the manual backup",
"Error creating the manual backup",
);
});
}}
@@ -1,3 +1,4 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -19,7 +20,7 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { Copy, TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
@@ -98,9 +99,26 @@ export const DeleteMongo = ({ mongoId }: Props) => {
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
<FormLabel className="flex items-center gap-2">
<span>
To confirm, type{" "}
<Badge
className="p-2 rounded-md ml-1 mr-1 hover:border-primary hover:text-primary-foreground hover:bg-primary hover:cursor-pointer"
variant="outline"
onClick={() => {
if (data?.name && data?.appName) {
navigator.clipboard.writeText(
`${data.name}/${data.appName}`,
);
toast.success("Copied to clipboard. Be careful!");
}
}}
>
{data?.name}/{data?.appName}&nbsp;
<Copy className="h-4 w-4 ml-1 text-muted-foreground" />
</Badge>{" "}
in the box below:
</span>
</FormLabel>
<FormControl>
<Input
@@ -70,7 +70,7 @@ export const ShowMongoEnvironment = ({ mongoId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to add environment");
toast.error("Error adding environment");
});
};

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