Compare commits

..

528 Commits

Author SHA1 Message Date
Mauricio Siu a0b550ace9 Merge pull request #2756 from niieani/bb/fix-null
fix: return an empty object if yaml file is empty
2025-10-05 12:10:55 -06:00
Mauricio Siu 7943c90d5d refactor: enhance middleware removal logic in Traefik configuration 2025-10-05 12:07:19 -06:00
Mauricio Siu fc3fceb858 refactor: improve Traefik middleware configuration handling and validation 2025-10-05 12:04:21 -06:00
Mauricio Siu 1804a7c301 refactor: remove unnecessary middleware checks in Traefik config generation 2025-10-05 11:26:46 -06:00
autofix-ci[bot] e97046c267 [autofix.ci] apply automated fixes 2025-10-05 17:14:11 +00:00
Bazyli Brzoska 080233a7cd fix: traefik needs middlewares to be empty/valid 2025-10-05 08:06:06 -07:00
Mauricio Siu be5d65a8e3 Merge pull request #2684 from sueffuenfelf/fix/docker-terminal-dropdown-containers
fix: docker terminal dropdown not showing containers for applications of type "docker-compose"
2025-10-05 00:51:11 -06:00
Mauricio Siu e934d4f4ce refactor: remove unused badgeStateColor variable in ShowDockerLogsStack component
- Eliminated the unused badgeStateColor variable to clean up the code.
- Improved overall readability and maintainability of the ShowDockerLogsStack component.
2025-10-05 00:48:07 -06:00
Mauricio Siu 586195b5c8 refactor: enhance DockerTerminalModal component for better prop handling
- Removed unnecessary conditional check for containerId in the main dialog open handler.
- Updated Terminal component to ensure serverId and containerId have default values, improving robustness and user experience.
2025-10-05 00:47:50 -06:00
Mauricio Siu c8320da716 refactor: simplify props destructuring in DockerTerminalModal component
- Updated the props destructuring to directly include `serverId` instead of using a conditional spread operator.
- Improved code readability by streamlining the object structure.
2025-10-05 00:46:27 -06:00
Mauricio Siu 8a9a0e49ce refactor: remove unused state in DockerTerminal component
- Eliminated the `isConnected` state variable as it was not being utilized.
- Cleaned up imports by removing unused `useState` hook.
2025-10-05 00:45:45 -06:00
Mauricio Siu aadb278e5f refactor: simplify WebSocket connection logic in DockerTerminal component
- Removed redundant checks for containerId before establishing WebSocket connection.
- Streamlined the connection setup and added the AttachAddon directly after the terminal is opened.
- Updated UI text to clarify the connection method.
2025-10-05 00:45:07 -06:00
Mauricio Siu 47a9bd9c86 Merge branch 'canary' into fix/docker-terminal-dropdown-containers 2025-10-05 00:43:48 -06:00
Mauricio Siu 739dc21bc0 Merge pull request #2679 from dennisimoo/custom-profile-picture
feat: add file upload support for custom profile pictures
2025-10-05 00:37:31 -06:00
Mauricio Siu fa4724d94e Update profile-form.tsx 2025-10-05 00:35:10 -06:00
autofix-ci[bot] 32454bab61 [autofix.ci] apply automated fixes 2025-10-05 06:30:46 +00:00
Mauricio Siu beb6f38204 Merge pull request #2599 from Harikrishnan1367709/separate-permission-for-deleting-environments-#2594
feat: Add Environment Deletion Permission Control-#2594
2025-10-05 00:26:54 -06:00
Mauricio Siu 3a0549bbd8 chore: update dokploy version to v0.25.5 in package.json 2025-10-05 00:26:37 -06:00
Mauricio Siu 4112ba9b10 refactor: reorganize user permission checks in AdvancedEnvironmentSelector
- Moved the check for user permissions to delete environments to a more logical position in the code.
- Removed redundant API query for environment data, streamlining the component's state management.
2025-10-05 00:25:18 -06:00
Mauricio Siu fbf57739b3 feat: add canDeleteEnvironments column to member table
- Introduced a new boolean column `canDeleteEnvironments` to the `member` table with a default value of false.
- Updated journal and snapshot metadata files to include the new migration details for this change.
2025-10-05 00:19:56 -06:00
Mauricio Siu e4f5a1d828 Merge branch 'canary' into separate-permission-for-deleting-environments-#2594 2025-10-05 00:19:01 -06:00
Mauricio Siu 3e09644877 Remove daily_jack_murdock SQL script and associated journal entry from the project. This change eliminates the canDeleteEnvironments column from the member table, streamlining the database schema. 2025-10-05 00:17:31 -06:00
Mauricio Siu 1ab576d260 Merge pull request #2598 from Harikrishnan1367709/separate-permission-for-creating-environments-#2593
feat: Add environment creation permission control-#2593
2025-10-05 00:16:39 -06:00
Mauricio Siu 0b0f507b49 feat: add functionality to create a new environment when a project is created
- Integrated the `addNewEnvironment` function into the project creation process.
- Ensured that the environment is associated with the current user and organization.
2025-10-05 00:15:02 -06:00
Mauricio Siu fa8722f6c8 feat: add canCreateEnvironments column to member table and update metadata
- Introduced a new boolean column `canCreateEnvironments` to the `member` table with a default value of false.
- Updated journal and snapshot metadata files to include the new migration details.
2025-10-05 00:09:23 -06:00
Mauricio Siu fb0ed494fc Merge branch 'canary' into separate-permission-for-creating-environments-#2593 2025-10-05 00:08:49 -06:00
Mauricio Siu 6d2728f5f0 chore: remove deprecated SQL migration files for member permissions
- Deleted SQL migration files `0111_magical_nova.sql` and `0112_serious_hellcat.sql` which added `canCreateEnvironments` and `canCreateEnvironmentsInProjects` columns to the `member` table.
- Updated journal and snapshot metadata files to reflect the removal of these migrations.
2025-10-05 00:08:02 -06:00
Mauricio Siu 8efc8b573c Merge pull request #2577 from robgraeber/patch-1
Fix swarm settings config placeholders
2025-10-05 00:04:24 -06:00
Mauricio Siu 644189064b Merge pull request #2232 from perinm/feature/stop-grace-period-2227
feat: Add stop_grace_period to swarm settings
2025-10-05 00:01:44 -06:00
Mauricio Siu 23c891d6fc feat: add stopGracePeriodSwarm column to multiple database tables and update journal and snapshot metadata 2025-10-04 23:57:13 -06:00
Mauricio Siu a3f9f9b7a1 Merge branch 'canary' into feature/stop-grace-period-2227 2025-10-04 23:45:59 -06:00
Mauricio Siu 83a7b8dce5 refactor: remove stop grace period swarm migrations and snapshots 2025-10-04 23:45:32 -06:00
autofix-ci[bot] e9b5699f8e [autofix.ci] apply automated fixes 2025-10-05 05:43:58 +00:00
Mauricio Siu f952f53fca Merge pull request #2678 from dennisimoo/update-logos
style: replace generic icons with Gotify and Ntfy brand logos
2025-10-04 23:43:17 -06:00
autofix-ci[bot] 60db2972c7 [autofix.ci] apply automated fixes 2025-10-05 05:42:41 +00:00
Mauricio Siu 143e4be9e6 Merge pull request #2744 from Captainsalem/canary
fix: correct typo in saveGitProvider function name
2025-10-04 23:36:20 -06:00
Mauricio Siu 18e553f239 Merge pull request #2764 from Dokploy/2530-new-user-email-invitation-does-not-render-correctly-on-osxs-mailapp
chore: update better-auth to version 1.3.26 and adjust dependencies i…
2025-10-04 21:53:39 -06:00
Mauricio Siu c41f447269 chore: downgrade better-auth to version v1.2.8-beta.7 in package.json files and update dependencies in pnpm-lock.yaml 2025-10-04 21:51:50 -06:00
Mauricio Siu dbc4f4e4c5 chore: update better-auth to version 1.3.26 and adjust dependencies in package.json files 2025-10-04 21:45:48 -06:00
Mauricio Siu 8594ad8ece Merge pull request #2763 from Dokploy/2645-github-auto-deploy-webhook-responds-404
2645 GitHub auto deploy webhook responds 404
2025-10-04 20:59:01 -06:00
Mauricio Siu 9edd69b10d refactor: remove console log from WebDomain component 2025-10-04 20:58:30 -06:00
Mauricio Siu 4a9684bbe4 refactor: simplify URL change warning in WebDomain component 2025-10-04 20:58:07 -06:00
Mauricio Siu 4f835c6c5e feat: add warning alert for URL changes in WebDomain component 2025-10-04 20:56:38 -06:00
Bazyli Brzoska 54853098a7 fix: return an empty object if yaml file is empty 2025-10-04 17:19:24 -07:00
artemis37 cdca2ea6d2 fix: correct typo in saveGitProvider function name
- Fixed "saveGitProdiver" to "saveGitProvider" in API router
- Updated corresponding component usage to maintain consistency
2025-10-02 02:44:47 +03:00
Mauricio Siu 9f5c2dbe92 chore: update version to v0.25.4 in package.json 2025-09-28 22:32:35 -06:00
Mauricio Siu 0f9505327f Merge pull request #2710 from SimonLoir/canary
fix: add environment in buildLink for docker compose deploy notifications
2025-09-27 15:14:48 -06:00
Simon Loir dd2902a57c fix: fix buildLink in docker compose deploy notifications 2025-09-27 16:50:25 +02:00
Mauricio Siu 0138a7c011 Merge pull request #2532 from monntterro/feat/gitea-http-support
feat: support cloning repositories over HTTP in Gitea integration
2025-09-27 03:17:08 -06:00
autofix-ci[bot] 845d2a3ac5 [autofix.ci] apply automated fixes 2025-09-27 09:15:31 +00:00
Mauricio Siu 4033bb84b2 Merge pull request #2640 from amirparsadd/patch-1
feat: support Arvancloud CDN detection
2025-09-27 03:14:12 -06:00
Mauricio Siu 43e96edcdd Merge pull request #2668 from alsmadi99/canary
feat(scheduler): auto-switch to 'Custom' on manual input
2025-09-27 03:13:00 -06:00
Mauricio Siu 2db388536f Merge pull request #2700 from dennisimoo/compose-alert
feat: add unsaved changes tracking and UI indication
2025-09-27 03:09:33 -06:00
Mauricio Siu 43876efc79 Merge pull request #2677 from dennisimoo/fix-position
style: move Deployments tab after Domains tab
2025-09-27 03:07:02 -06:00
Mauricio Siu e7c7545c02 Merge pull request #2706 from Dokploy/2673-bitbucket-deployments-are-broken-auth-token-wont-work
fix(bitbucket): enhance Bitbucket authentication handling
2025-09-27 02:58:49 -06:00
autofix-ci[bot] 77705381cd [autofix.ci] apply automated fixes 2025-09-27 08:56:28 +00:00
Mauricio Siu 5fdf82a27f refactor(bitbucket): remove debug console logs from repository cloning process
- Removed console logs for clone URL and repository information to clean up the output during the cloning process.
2025-09-27 02:55:42 -06:00
Mauricio Siu 6bd5b1f71f fix(bitbucket): enhance Bitbucket authentication handling
- Added support for Bitbucket email and workspace name in the authentication process.
- Updated the clone URL generation to use the correct format for API tokens.
- Improved error handling to ensure required fields are provided for both API tokens and app passwords.
- Added console logs for debugging clone URL and repository information during cloning.
2025-09-27 02:55:06 -06:00
Mauricio Siu 17d6830b66 Merge pull request #2705 from Dokploy/2670-bug-deployments-are-mark-as-running-when-they-never-ended-vps-shutdown
2670 bug deployments are mark as running when they never ended vps shutdown
2025-09-27 02:23:53 -06:00
Mauricio Siu a845eba320 Merge pull request #2696 from Harikrishnan1367709/Most-services-has-no-effect-#2691
Feat: "Most services" sorting to count total services across environments -2691
2025-09-27 02:22:58 -06:00
Mauricio Siu 2f4ec9f35f fix(deployment): reintroduce deployment cancellation during server initialization
- Added the call to initCancelDeployments back into the server initialization process to ensure that deployment cancellations are handled correctly in all environments.
2025-09-27 02:21:02 -06:00
autofix-ci[bot] b725861b55 [autofix.ci] apply automated fixes 2025-09-27 08:20:36 +00:00
Mauricio Siu 6fa8f63277 fix(deployment): correct deployment cancellation logic and ensure proper status update
- Updated the initCancelDeployments function to set the status of running deployments to 'cancelled' instead of 'error'.
- Reintroduced the call to initCancelDeployments in the server initialization process to ensure cancellations are handled correctly.
2025-09-27 02:20:07 -06:00
Mauricio Siu ac6bdf60ec feat(deployment): add 'cancelled' status to deployment and implement cancellation logic
- Updated the deployment status enum to include 'cancelled'.
- Added a new utility function to handle the cancellation of deployments, setting their status to 'error'.
- Enhanced the status tooltip component to display 'Cancelled' when the status is 'cancelled'.
- Created a new SQL migration to add the 'cancelled' value to the deploymentStatus type.
2025-09-27 02:15:43 -06:00
randomperson12344 db292e6949 feat: add unsaved changes tracking and UI indication 2025-09-26 20:13:09 -07:00
montero 085f6bbbb7 refactor(gitea): extract clone URL construction into a reusable function 2025-09-26 22:01:54 +03:00
autofix-ci[bot] cbdc4e4a20 [autofix.ci] apply automated fixes 2025-09-26 08:48:23 +00:00
HarikrishnanD ee3ff18feb fix: correct "Most services" sorting to count total services across environments - Fix sorting logic to count actual services instead of environment count - Projects now properly sort by total service count in descending order - Resolves issue where "Most services" showed ascending order instead of descending -#2691 2025-09-26 14:15:58 +05:30
autofix-ci[bot] 598ecb8c6e [autofix.ci] apply automated fixes 2025-09-25 06:39:08 +00:00
Sofien Scholze 1d5a523b9e fix: docker terminal dropdown not showing containers for application of type "docker-compose" 2025-09-24 22:52:20 +02:00
Lucas Manchine 4bced9ede0 fix: db migrations for stop grace period swarm 2025-09-24 12:00:09 -03:00
Lucas Manchine e35aeef4e2 fix: db migrations for stop grace period swarm 2025-09-24 11:53:02 -03:00
Lucas Manchine 5e89ffbf4f fix: extend-database-schemas-with-stopgraceperiodswarm 2025-09-24 10:50:04 -03:00
Lucas Manchine 21de6bf167 test: add missing test 2025-09-24 10:26:36 -03:00
Lucas Manchine 291edce62f fixing migration 2025-09-24 10:02:15 -03:00
Lucas Manchine 59be1c5941 fix: coerce-stopgraceperiodswarm-to-number 2025-09-24 09:54:54 -03:00
Lucas Manchine 2141e4b174 Merge branch 'canary' into feature/stop-grace-period-2227 2025-09-24 08:52:32 -03:00
randomperson12344 df0fb340ad feat: add file upload support for custom profile pictures 2025-09-23 22:32:32 -07:00
randomperson12344 190ccfa91f style: replace generic icons with Gotify and Ntfy brand logos 2025-09-23 21:04:55 -07:00
randomperson12344 f5084dd5fb feat(ui): move Deployments tab to position 4 after Domains tab 2025-09-23 19:23:43 -07:00
autofix-ci[bot] 1b603d84d7 [autofix.ci] apply automated fixes 2025-09-22 19:11:08 +00:00
Mohammad Alsmadi cf2c89d136 feat(scheduler): auto-switch to 'Custom' on manual input 2025-09-22 13:35:52 +04:00
Amirparsa Baghdadi 95de98e94d close string 2025-09-22 12:37:21 +03:30
Mauricio Siu 569d43ae7f Merge pull request #2525 from divaltor/bitbucket-api-token
feat(bitbucket): Deprecate App password and replace it with API token
2025-09-21 15:18:40 -06:00
Mauricio Siu d22ed9b569 refactor(bitbucket): streamline extractCommitedPaths function by passing Bitbucket object directly 2025-09-21 15:15:21 -06:00
Mauricio Siu 8b88c85b37 refactor(bitbucket): simplify extractCommitedPaths function by using Bitbucket type and centralized header generation 2025-09-21 15:14:15 -06:00
Mauricio Siu 11fbd047d0 feat(bitbucket): enhance API token creation instructions in Bitbucket provider settings 2025-09-21 14:13:55 -06:00
Mauricio Siu 69af9c0312 refactor(gitea): update repository and branch fetching to use pagination with /user/repos and /branches endpoints 2025-09-21 14:10:00 -06:00
Mauricio Siu 063d51e442 feat(bitbucket): add bitbucketEmail field to Bitbucket provider settings and update related API and database schema 2025-09-21 13:54:53 -06:00
Mauricio Siu 0a789e1d6f feat(bitbucket): add apiToken column to the Bitbucket table and update migration journal with new entry for 0111_mushy_wolfsbane 2025-09-21 03:10:52 -06:00
Mauricio Siu 671cd497fd Merge branch 'canary' into bitbucket-api-token 2025-09-21 03:10:37 -06:00
Mauricio Siu 8ddc254252 chore: remove '0110_smiling_slapstick' migration and associated journal entry 2025-09-21 03:10:30 -06:00
Mauricio Siu 2668e22302 feat(bitbucket): add apiToken column to Bitbucket table and update migration journal 2025-09-21 03:09:32 -06:00
Mauricio Siu 37145fbdf2 chore: bump version to v0.25.3 in package.json 2025-09-21 03:02:43 -06:00
Mauricio Siu 6847d8dbef Merge pull request #2516 from cheetahbyte/fix/special-characters-passwords
fix(registries): special character passwords not working in registry creation.
2025-09-21 02:45:43 -06:00
Mauricio Siu 032bcb7459 Merge pull request #2657 from Dokploy/2529-renaming-a-git-provider-wont-update-the-external-link-url
feat: add appName field to GitHub provider settings and update relate…
2025-09-21 02:41:11 -06:00
Mauricio Siu 68be7a259f Merge pull request #2656 from Dokploy/2533-unknown-tag-reset-error-in-domains-when-extending-docker-compose-configuration
refactor: replace js-yaml with yaml package for YAML parsing and stri…
2025-09-21 02:40:40 -06:00
Mauricio Siu 7d682870ff feat: add appName field to GitHub provider settings and update related API and database schema 2025-09-21 02:39:20 -06:00
Mauricio Siu d1a1a80c77 refactor: further standardize YAML parsing in test files by replacing load with parse 2025-09-21 02:38:16 -06:00
Mauricio Siu 3d7dc82232 refactor: update test files to consistently use parse function for YAML parsing 2025-09-21 02:35:36 -06:00
Mauricio Siu fedc88eb40 refactor: consistently replace load function with parse for YAML parsing in all test files 2025-09-21 02:28:30 -06:00
Mauricio Siu 5d0f6a4657 refactor: replace load function with parse for YAML parsing in test files 2025-09-21 02:27:16 -06:00
Mauricio Siu 4718461405 refactor: update YAML parsing from js-yaml to yaml package in test files 2025-09-21 02:24:41 -06:00
Mauricio Siu 80b22d9458 refactor: replace js-yaml with yaml package for YAML parsing and stringifying across the application 2025-09-21 02:20:20 -06:00
Mauricio Siu 8fa5fe7f2c Merge pull request #2654 from Dokploy/2018-traefik-never-start-error-read-etctraefiktraefikyml-is-a-directory
2018 traefik never start error read etctraefiktraefikyml is a directory
2025-09-21 01:39:06 -06:00
Mauricio Siu 4ced8bec96 feat: add completion message and exit process after Dokploy setup 2025-09-21 01:35:46 -06:00
Mauricio Siu 9ecb770a01 fix: enhance Traefik setup by adding directory checks and cleanup for existing config files 2025-09-21 01:31:21 -06:00
Mauricio Siu 8ac586b2f7 Merge pull request #2653 from Dokploy/2554-ai-assistant-is-broken-in-v025
fix: handle optional configFiles in template details and improve mapp…
2025-09-21 01:11:27 -06:00
Mauricio Siu 0a1800ba6d fix: adjust layout by removing unnecessary flex class from password input container 2025-09-21 01:09:12 -06:00
Mauricio Siu f13028ee70 fix: handle optional configFiles in template details and improve mapping safety 2025-09-21 01:07:25 -06:00
Mauricio Siu b6b6b9f2ce Merge pull request #2652 from Dokploy/2630-backups-dont-get-deleted-when-backup-fails
fix: enhance error handling in volume backup process by adding cleanu…
2025-09-21 00:27:45 -06:00
Mauricio Siu f46637b8e1 fix: enhance error handling in volume backup process by adding cleanup for .tar files 2025-09-21 00:26:48 -06:00
Mauricio Siu 948ed2cc0d fix: improve registry tag construction to conditionally include registry URL 2025-09-21 00:13:56 -06:00
Mauricio Siu a536c977f0 Merge pull request #2651 from Dokploy/2633-error-parsing-reference-app-aaa-9bkzznlatest-is-not-a-valid-repositorytag-invalid-reference-format
fix: update registry tag construction to handle optional registry URL
2025-09-21 00:11:21 -06:00
Mauricio Siu 8524cd0972 fix: update registry tag construction to handle optional registry URL 2025-09-21 00:09:19 -06:00
Mauricio Siu ac1e51cd11 Merge pull request #2650 from Dokploy/2638-overlay-network-not-working-across-nodes
refactor: replace getPublicIpWithFallback with getLocalServerIp for i…
2025-09-21 00:01:28 -06:00
Mauricio Siu ca243d7259 refactor: replace getPublicIpWithFallback with getLocalServerIp for improved local IP retrieval 2025-09-20 23:57:38 -06:00
Mauricio Siu e1ce54c159 Merge pull request #2622 from Harikrishnan1367709/Compose-does-not-display-the-domain-under-dashboard/projects-#2606
fix: Display Compose service domains in projects dashboard (#2606)
2025-09-20 16:37:49 -06:00
Mauricio Siu 031302d808 Merge pull request #2643 from nimone/patch-1
fix: prevent the shrinking of icon button for view mode on add template dialog
2025-09-20 16:36:57 -06:00
Mauricio Siu 5e01505e4d fix: update input class for better responsiveness in add template component 2025-09-20 16:36:36 -06:00
Mauricio Siu c423724972 Merge pull request #2614 from Harikrishnan1367709/Profile-email-field-accepts-empty-values-causing-sign-in-issues-#2613
Fix profile email validation to prevent empty values causing sign-in issues-#2613
2025-09-20 16:34:20 -06:00
Mauricio Siu f1f7639708 Merge pull request #2624 from dragospaulpop/dragospaulpop-patch-cloneRawGitlabRepositoryRemote
Fix: Update gitlab.ts cloneRawGitlabRepositoryRemote to use gitlabBranch
2025-09-20 16:32:19 -06:00
Mauricio Siu 9ef1a76a85 Merge pull request #2582 from yigitahmetsahin/feat/improve-db-backups
feat(backups): make mariadb backups non-blocking
2025-09-20 16:31:16 -06:00
Nishant Mogha 30b66a4828 fix: prevent shrinking icon button for view mode on add template 2025-09-19 21:13:20 +05:30
Amirparsa Baghdadi 4416ca9cd2 Add arvancloud to CDNs 2025-09-19 15:58:22 +03:30
Dragos-Paul Pop f2ead66890 Update gitlab.ts cloneRawGitlabRepositoryRemote to use gitlabBranch
Cloning a GitLab repository for a compose service to a remote server incorrectly used the "branch" column from Postgres' "compose" table instead of the "gitlabBranch" column causing an error.
2025-09-17 11:48:12 +03:00
HarikrishnanD 64475bbb13 fix: Compose domain display logic in projects dashboard - Uncommented the commented-out Compose domain rendering code in ShowProjects.tsx - Fixed data structure to properly iterate through project.environments and env.compose - Added proper condition checking for compose services - Compose services now display their domains in the projects dashboard dropdown - Resolves issue #2606 where template-deployed Compose services didn't show domains 2025-09-17 14:07:03 +05:30
autofix-ci[bot] c1896f8877 [autofix.ci] apply automated fixes 2025-09-16 07:47:55 +00:00
HarikrishnanD d13975adac fix: add email validation to profile form to prevent empty values - Add email format and required validation to profile form schema - Add email validation to API schema and service layer - Improve error handling in user update mutation - Fixes issue where users could save empty email causing sign-in failures -#2613 2025-09-16 13:11:22 +05:30
Mauricio Siu d9398b9558 feat(workers): add third worker and increase concurrency for existing workers 2025-09-15 23:43:27 -06:00
Mauricio Siu 788dbe4050 chore(package): bump version from v0.25.1 to v0.25.2 2025-09-15 23:23:03 -06:00
Mauricio Siu 6934f44778 Merge pull request #2573 from Harikrishnan1367709/Duplicating-a-service-does-not-refresh-the-list-afterwards-#2565-Harikrishnan
feat: Auto-refresh services list when duplicating to same environment
2025-09-15 23:18:40 -06:00
Mauricio Siu b8e9602538 feat(bitbucket): update Bitbucket token management and add API token column to database 2025-09-15 23:10:50 -06:00
Mauricio Siu afca968853 chore: remove unused migration and associated journal entry for '0110_dry_golden_guardian' 2025-09-15 23:03:43 -06:00
Mauricio Siu 457a6db00f Merge pull request #2562 from sundakai/canary
fix:traefik 3.5.0 error
2025-09-15 22:59:11 -06:00
Mauricio Siu 81f89a0796 Merge pull request #2597 from demondayza/canary
fix: fix typo for Github clone
2025-09-15 22:27:00 -06:00
autofix-ci[bot] 65c5974b4f [autofix.ci] apply automated fixes 2025-09-12 13:49:51 +00:00
autofix-ci[bot] bdf0a932fe [autofix.ci] apply automated fixes 2025-09-12 13:46:33 +00:00
HarikrishnanD c355eafc95 feat: add environment deletion permission control - Add canDeleteEnvironments field to member table - Implement permission validation in environment deletion endpoint - Add UI toggle in user permissions modal - Hide delete buttons for users without permission - Maintain backward compatibility for owners/admins #2594 2025-09-12 19:09:30 +05:30
Andrew Margetts d8a98f3936 fix: fix typo for Github clone 2025-09-12 15:27:10 +02:00
HarikrishnanD 30b28afbac feat: add canCreateEnvironments permission for environment creation - Add database field and API validation - Implement permission checking in environment creation - Add UI toggle in user permissions modal - Hide create button for unauthorized users Fixes #2593 2025-09-12 17:56:02 +05:30
Yigit SAHIN c9715b19a3 feat(backups): make mariadb backups non-blocking closes #2443 2025-09-10 11:27:22 +03:00
Rob Graeber 1a940580ae Fix swarm settings config placeholders 2025-09-09 18:03:02 -06:00
autofix-ci[bot] ec11325165 [autofix.ci] apply automated fixes 2025-09-09 16:40:00 +00:00
HarikrishnanD abcbd2d599 feat: auto-refresh services list when duplicating to same environment - Add cache invalidation for environment.one and environment.byProjectId queries - Fix issue where duplicated services weren't visible until hard refresh - Ensure proper invalidation when duplicating to current environment - Resolves #2565 2025-09-09 22:07:40 +05:30
永恒 1664ae9b92 fix traefik 3.5.0 error
fix traefik error:"both Docker and Swarm labels are defined"
2025-09-08 12:26:36 +08:00
Mauricio Siu 24729f35ec fix(traefik): remove error toast on dashboard action failure 2025-09-07 14:03:10 -06:00
Mauricio Siu 3eaeaa1db4 chore(package): bump version from v0.25.0 to v0.25.1 2025-09-07 13:31:38 -06:00
Mauricio Siu de4a00f1e9 Merge pull request #2556 from Dokploy/2552-traefik-container-no-auto-start
feat(settings): add error handling for unsupported resource types in …
2025-09-07 13:31:03 -06:00
Mauricio Siu 2f5cd620c5 feat(settings): add error handling for unsupported resource types in Traefik setup
- Introduced error handling for unsupported resource types in `readPorts` and `writeTraefikSetup` functions.
- Enhanced `initializeStandaloneTraefik` to include image pulling with error logging for better debugging.
2025-09-07 13:26:19 -06:00
Mauricio Siu 1763000070 Merge pull request #2545 from Dokploy/feat/clean-build-queue-on-build
feat(deployment): add cancellation functionality queue for deployments
2025-09-06 22:12:44 -06:00
Mauricio Siu 3519913886 fix(deployment): update stuck build notification time from 9 to 10 minutes 2025-09-06 22:09:48 -06:00
Mauricio Siu 63e578f13c refactor(deployment): update cancellation input schemas for applications and composes
- Removed the previous cancellation schemas for deployments.
- Replaced them with a unified input schema for finding applications and composes during cancellation requests.
- Ensured that the cancellation logic now utilizes the new input structure for better consistency.
2025-09-06 22:08:59 -06:00
autofix-ci[bot] d80ada7c00 [autofix.ci] apply automated fixes 2025-09-07 04:06:08 +00:00
Mauricio Siu 766cd20e90 feat(deployment): improve stuck deployment detection and update status
- Enhanced the stuck deployment check to only consider the most recent deployment.
- Updated the logic to correctly identify if the most recent deployment has been running for more than 9 minutes.
- Added functionality to update the deployment status to "done" upon application and compose cancellation.
2025-09-06 22:05:39 -06:00
Mauricio Siu 4e69c70697 feat(deployment): add cancellation functionality for deployments
- Introduced a new endpoint for cancelling deployments, allowing users to cancel both application and compose deployments.
- Implemented validation schemas for cancellation requests.
- Enhanced the deployment dashboard to provide a cancellation option for stuck deployments.
- Updated server-side logic to handle cancellation requests and send appropriate events.
2025-09-06 21:53:15 -06:00
Mauricio Siu 3b7d009841 fix(search-command): remove console log for project debugging 2025-09-06 20:36:01 -06:00
Mauricio Siu b4e29dab39 Merge pull request #2515 from divaltor/filter-projects-shortcut
feat(input): Add focus by Cmd + K shortcut to search input
2025-09-06 14:32:52 -06:00
Mauricio Siu 090ec2b3b9 Merge pull request #2540 from robgraeber/canary
fix: typo and improve grammar
2025-09-06 14:32:41 -06:00
Mauricio Siu 57dc24bcb1 feat(search-command): enhance service extraction and project navigation
- Introduced a new function `extractAllServicesFromProject` to aggregate services from all environments within a project, including environment details.
- Updated the project selection logic to navigate to the production environment of a project.
- Modified the display of services to include the environment name alongside the service name in the search results.
2025-09-06 14:30:59 -06:00
Mauricio Siu f630b889c6 Merge branch 'canary' into filter-projects-shortcut 2025-09-06 14:19:35 -06:00
Rob Graeber a2abb205fd fix: typo and improve grammar 2025-09-06 13:19:13 -07:00
Mauricio Siu 1f2dabb16b Merge pull request #2429 from CatPaulKatze/feat/ntfy
feat(notification): add ntfy notifications
2025-09-06 14:17:27 -06:00
Mauricio Siu ffb69fedff feat: Add 'ntfy' notification type and related database schema changes
- Introduced a new notification type 'ntfy' to the public.notificationType enum.
- Created a new table 'ntfy' with fields for notification ID, server URL, topic, access token, and priority.
- Updated the existing 'notification' table to include a foreign key reference to the new 'ntfy' table.
2025-09-06 14:13:47 -06:00
Mauricio Siu fbc087bd84 Merge branch 'canary' into feat/ntfy 2025-09-06 14:12:06 -06:00
Mauricio Siu ccb995cb7d chore: remove SQL files and journal entries for 'bitter_starfox' and 'needy_rocket_raccoon' 2025-09-06 14:11:39 -06:00
Mauricio Siu 02685fde9d Merge pull request #2507 from Harikrishnan1367709/Allow-setting-a-title/description-for-deployments-via-API-or-CLI-#1485-Harikrishnan
feat: Add custom title/description support for API/CLI deployments (#1485)
2025-09-06 14:00:26 -06:00
autofix-ci[bot] fc2bd44983 [autofix.ci] apply automated fixes 2025-09-06 19:49:09 +00:00
Mauricio Siu 30a2d78a5b Merge pull request #2502 from Harikrishnan1367709/Issue-1852-Harikrishnan
feat: Add default "Dokploy" option to server selection dropdown (#1852)
2025-09-06 13:47:42 -06:00
autofix-ci[bot] 081ba60f6e [autofix.ci] apply automated fixes 2025-09-06 19:36:11 +00:00
Lucas Manchine b7e2df6d6a refactor: clean up stopGracePeriodSwarm assignment formatting │
│                                                                                                                                                                                                                                                                                                                                                                                                    │
│   - Improve code readability by condensing multi-line assignment                                                                                                                                                                                                                                                                                                                                   │
│   - Maintain consistent formatting with other field assignments                                                                                                                                                                                                                                                                                                                                    │
│   - No functional changes, formatting only
2025-09-05 15:34:03 -03:00
Lucas Manchine 85e3a92877 feat(swarm): add stop grace period configuration for Docker Swarm services
- Add stopGracePeriodSwarm field to application schema for configuring container shutdown grace period
- Update swarm settings UI to include nanosecond input for stop grace period
- Regenerate migration as 0110 to resolve sequence conflict with canary branch
- Clean up commented debug code and reorganize imports

The stop grace period allows users to specify how long Docker should wait before forcefully
terminating a container during shutdown, improving graceful shutdown handling for applications.
2025-09-05 13:23:46 -03:00
Lucas Manchine c2eaa78724 refactor: clean up stopGracePeriodSwarm implementation │
│                                                                                                                                                                                                                                                                                                                               │
│   - Remove commented debug code                                                                                                                                                                                                                                                                                               │
│   - Reorganize imports for better readability
2025-09-05 13:05:46 -03:00
Lucas Manchine 270b4d4edc Merge branch 'canary' into feature/stop-grace-period-2227-alt 2025-09-05 12:34:17 -03:00
Vlad Vladov 2d41db7f37 feat(input): Replace Input with FocusShortcutInput 2025-09-05 18:19:16 +03:00
Vlad Vladov d0f54f2067 feat(input): Add focus by Cmd + K shortcut to search input 2025-09-05 18:13:23 +03:00
Vlad Vladov a6ca41f91f feat(bitbucket): Re-generate migration 2025-09-05 18:09:18 +03:00
Vlad Vladov b2b649c5cd refactor(bitbucket): Extract duplicated code to a function 2025-09-05 18:08:37 +03:00
autofix-ci[bot] 225c398d31 [autofix.ci] apply automated fixes 2025-09-05 18:08:37 +03:00
Vlad Vladov 07b99bd4e4 style(ui): Remove tooltip 2025-09-05 18:08:37 +03:00
autofix-ci[bot] 652e8910f4 [autofix.ci] apply automated fixes 2025-09-05 18:08:36 +03:00
Vlad Vladov e04e25385d feat(bitbucket): Deprecate App password and replace it with API token 2025-09-05 18:08:35 +03:00
Lucas Manchine da9df3e239 testing changes 2025-09-05 11:49:32 -03:00
Paul Sommer 6833713697 perf: remove unnecessary decoration boolean on the ntfy database schema 2025-09-05 11:35:28 +02:00
Mauricio Siu d0489f6e11 Merge branch 'canary' into Issue-1852-Harikrishnan 2025-09-05 03:12:01 -06:00
Mauricio Siu 39872720dd refactor: remove debug logging from Docker resource type determination functions 2025-09-05 03:00:57 -06:00
Mauricio Siu b90f0135d4 refactor: simplify Docker resource type determination logic by consolidating command structure 2025-09-05 02:50:37 -06:00
Mauricio Siu 35fc04dc8f feat: enhance error handling and logging in Docker resource type determination 2025-09-05 02:35:06 -06:00
Mauricio Siu c6509efa65 feat: add debug logging for resource name and command in Docker resource type determination 2025-09-05 02:11:46 -06:00
Mauricio Siu 3891798b17 Merge pull request #2527 from Dokploy/fix/connect-network-after-creation-remote-servers
Fix/connect network after creation remote servers
2025-09-05 01:52:54 -06:00
Mauricio Siu 3662c1a684 fix: change Traefik container restart policy to 'always' and ensure it connects to the dokploy network 2025-09-05 01:49:47 -06:00
Mauricio Siu d96e9071f2 feat: add logging for resource type determination and error handling in Docker resource management 2025-09-05 01:47:12 -06:00
Mauricio Siu e637a4ad99 Merge pull request #2526 from Dokploy/2480-backup-process-exposes-s3-credentials-in-logs
feat: add validation to prevent use of 'production' as environment na…
2025-09-05 01:16:28 -06:00
Mauricio Siu 1ce15da7ce feat: add validation to prevent use of 'production' as environment name in creation and update operations, enhancing error handling in environment management 2025-09-05 01:14:44 -06:00
Mauricio Siu 0dca1b2216 Merge pull request #2489 from typed-sigterm/patch-2
fix: print error when docker build fails
2025-09-05 01:08:11 -06:00
Mauricio Siu c73a14a379 Merge branch 'canary' into patch-2 2025-09-05 01:07:51 -06:00
Mauricio Siu 392e3434c4 refactor: make database root password optional in schema and mutation logic, enhancing flexibility in database configuration 2025-09-05 01:01:26 -06:00
Mauricio Siu e3f3426f1c refactor: remove redundant password requirement validation from database schemas, improving consistency across database configurations 2025-09-05 01:00:18 -06:00
Mauricio Siu a09cd06eea refactor: streamline conditional rendering for service creation dropdown in EnvironmentPage, improving code readability and maintainability 2025-09-05 00:56:37 -06:00
Mauricio Siu 87a41ca710 Merge pull request #2499 from Dokploy/324-environmentfoldergroup-features-on-projects
324 environmentfoldergroup features on projects
2025-09-05 00:25:34 -06:00
Mauricio Siu 35b7b5bd68 feat: implement environment access control and service filtering based on user permissions, enhancing security and usability in environment management 2025-09-05 00:23:01 -06:00
Mauricio Siu 16c37c3ceb feat: add accessedEnvironments field to user and member schemas, enhancing permission management for environment access 2025-09-05 00:13:04 -06:00
Mauricio Siu 42548f310e refactor: simplify project selection logic in EnvironmentPage by removing unnecessary filters, improving readability and performance 2025-09-04 23:50:10 -06:00
Mauricio Siu 47b66d0dc3 refactor: enhance access control in environment, mount, port, rollback, and schedule routers to ensure users can only interact with resources belonging to their organization 2025-09-04 23:32:25 -06:00
HarikrishnanD 32cbc5b4b7 feat: Add custom title/description for deployments via API/CLI - Add optional title/description fields to deployment schemas - Update TRPC and external API endpoints - Replace generic "Manual deployment" with custom titles - Maintain backward compatibility with default values Fixes #1485 2025-09-04 19:12:29 +05:30
Typed SIGTERM 15171622df fix 2025-09-04 20:08:50 +08:00
HarikrishnanD 46f1af3bb3 feat(ui): add conditional server dropdown with Dokploy default option - Add IS_CLOUD flag support for server selection dropdown - Show "Dokploy" as default option in self-hosted environments - Hide dropdown when no remote servers exist - Add conditional placeholder text and server count display - Handle "dokploy" value in form submission (converts to undefined) - Apply changes to all relevant components: add-application, add-compose, add-template, add-database, add-certificate, and AI step-one Resolves #1852 2025-09-04 13:54:19 +05:30
Mauricio Siu d199a54033 refactor: update environment invalidation logic in AdvancedEnvironmentSelector to use byProjectId, improving data consistency and clarity 2025-09-03 23:56:31 -06:00
Mauricio Siu fb749cd862 feat: implement comprehensive environment variable resolution in preparation functions, enhancing flexibility and support for nested references across services and environments 2025-09-03 21:41:11 -06:00
Mauricio Siu 4c5771b55b feat: add EnvironmentVariables component for managing environment variables, enhancing project configuration capabilities 2025-09-03 21:24:59 -06:00
Mauricio Siu 7e1de62ab1 refactor: enhance environment selector component and database schema to support new environment field, improving clarity and functionality in project management 2025-09-03 21:19:12 -06:00
Mauricio Siu d67644e52f refactor: adjust environment page to correctly display project name and reintroduce duplicate project functionality, enhancing user navigation and clarity 2025-09-03 21:11:54 -06:00
Mauricio Siu 52e21dab4e refactor(ui): simplify server selection logic across components - Remove redundant server count check in server selection dropdowns across multiple components (AddApplication, AddCompose, AddDatabase, AddTemplate, StepOne, AddCertificate) to streamline UI behavior. 2025-09-03 20:45:47 -06:00
autofix-ci[bot] 4a3a7fa47b [autofix.ci] apply automated fixes 2025-09-04 02:43:53 +00:00
autofix-ci[bot] 68945c6888 [autofix.ci] apply automated fixes 2025-09-03 18:17:04 +00:00
Leonhard Breuer 146d82b6c4 feat: use printf instead of echo 2025-09-03 20:12:16 +02:00
Leonhard Breuer 02215d4e21 fix: use new command for registry updates 2025-09-03 19:59:17 +02:00
Leonhard Breuer 4ca05414af fix: use shellsafe docker command
- add `shEscape` function - add `safeDockerLoginCommand` - use the new
functions to contruct better registry login command
2025-09-03 19:52:01 +02:00
Mauricio Siu aa7e382818 feat(readme): add sponsorship section for Tuple with logo 2025-09-03 03:00:48 -06:00
Mauricio Siu 87a9ed46ba refactor: update service extraction logic to utilize environment data, enhancing clarity and consistency in monitoring setup 2025-09-03 02:58:38 -06:00
HarikrishnanD 90d9880301 feat: add custom title/description support for API/CLI deployments - Add optional title and description fields to deployment schemas - Update TRPC endpoints to accept custom deployment titles/descriptions - Update external API to support custom deployment metadata - Maintain backward compatibility with existing deployments - Resolves issue #1485: Allow setting title/description for deployments via API/CLI 2025-09-03 09:05:33 +05:30
HarikrishnanD 940b9967b8 feat(ui): add default "Dokploy" option to server selection dropdown - Add "Dokploy" as default option in server selection dropdowns - Hide dropdown when only one server is available (servers.length <= 1) - Show dropdown only when multiple servers exist (servers.length > 1) - Update placeholder text from "Select a Server" to "Dokploy" - Fix issue where users couldn't switch back to default server - Update form submission logic to handle "dokploy" default value - Apply changes to all deployment components (application, compose, template, database, certificate, AI) Resolves #1852 2025-09-02 19:17:46 +05:30
Mauricio Siu 741085466b refactor: remove projectId references from service components, streamlining navigation and enhancing clarity in environment context 2025-09-02 00:25:09 -06:00
Mauricio Siu 11b0e21728 refactor: replace projectId with environmentId in database schema, enhancing clarity and consistency in environment management across services 2025-09-02 00:18:36 -06:00
autofix-ci[bot] 990b174110 [autofix.ci] apply automated fixes 2025-09-02 05:24:22 +00:00
Mauricio Siu 4c4c72bc9c refactor: update permissions handling to extract services from environments, improving data structure and clarity in user permissions management 2025-09-01 23:23:58 -06:00
autofix-ci[bot] 8f446d04f3 [autofix.ci] apply automated fixes 2025-09-02 05:20:20 +00:00
Mauricio Siu e8a5f9c0a8 refactor: restructure application and rollback context to encapsulate project within environment, improving data organization and clarity across services 2025-09-01 23:19:53 -06:00
autofix-ci[bot] c57c231c32 [autofix.ci] apply automated fixes 2025-09-02 05:16:09 +00:00
Mauricio Siu 8194929558 refactor: improve project navigation logic by ensuring proper handling of projectId and environmentId, enhancing routing clarity and user experience 2025-09-01 23:15:44 -06:00
autofix-ci[bot] 4a07118acd [autofix.ci] apply automated fixes 2025-09-02 05:10:56 +00:00
Mauricio Siu be9e19e708 refactor: enhance project and environment handling across components and services by replacing projectId with environmentId, improving context clarity and authorization checks 2025-09-01 23:10:37 -06:00
Mauricio Siu 3e7eff11cd refactor: update application deployment logic to utilize environment context for project name and organization ID, enhancing clarity and consistency across services 2025-09-01 22:51:35 -06:00
Mauricio Siu f8ebf77575 Merge pull request #2493 from nktnet1/fix-server-schedule-responsiveness
fix(ui): schedule responsiveness
2025-09-01 22:46:40 -06:00
Mauricio Siu de3c845ab0 refactor: update duplicate project logic to use 'existing-environment' for improved clarity in project duplication context 2025-09-01 22:45:57 -06:00
autofix-ci[bot] cb992259cf [autofix.ci] apply automated fixes 2025-09-02 04:42:24 +00:00
Mauricio Siu 883c3f9739 refactor: update DuplicateProject and AdvancedEnvironmentSelector components to utilize environmentId for improved context handling; enhance UI with project and environment selection features for better user experience 2025-09-01 22:40:51 -06:00
Mauricio Siu 766890192d refactor: streamline environment selector by utilizing findEnvironmentById for type definition; enhance service presence checks and UI layout for improved clarity 2025-09-01 21:33:13 -06:00
Mauricio Siu 1a9f131d39 refactor: enhance environment selector with service presence checks and alert notifications; update navigation links to include environment context for improved user experience 2025-09-01 21:18:56 -06:00
Mauricio Siu 59cbc8ee0d refactor: update environment selector and API routes to utilize environmentId for service management; enhance UI with Badge component for production environments 2025-09-01 21:09:30 -06:00
Mauricio Siu e9322fc900 refactor: add environment name links to service components for improved navigation and context clarity 2025-09-01 20:58:22 -06:00
Mauricio Siu 39d48d8bdf refactor: update API and dashboard components to replace projectId with environmentId for improved context handling and authorization checks 2025-09-01 20:39:58 -06:00
Mauricio Siu 399bcb0302 refactor: update project and API components to utilize environment context for organization authorization checks and enhance service retrieval methods 2025-09-01 20:36:03 -06:00
Mauricio Siu e0b6a8627a refactor: update database service components to utilize environment context for project name and organization authorization checks 2025-09-01 20:15:05 -06:00
Mauricio Siu ecf7ae924f refactor: update routing in dashboard components to include environment context; add new service pages for MongoDB, MySQL, PostgreSQL, Redis, and MariaDB 2025-09-01 20:12:14 -06:00
Mauricio Siu d57a0cf439 refactor: update API routes and services to use environment context for organization authorization checks; enhance service retrieval methods to include environment details 2025-09-01 20:05:36 -06:00
Mauricio Siu 52d2bd2114 refactor: remove EnvironmentManagement component and related environment selector from project dashboard; update environment page to use Badge component for production label 2025-09-01 19:52:30 -06:00
Mauricio Siu 72f8a28f4f refactor: update project structure to use environmentId instead of projectId across components and API routes; implement environment management features 2025-09-01 19:48:20 -06:00
Mauricio Siu 6fc325fe95 feat(environment): implement environment management with create, duplicate, and delete functionalities; add environment schema and database migrations 2025-09-01 17:36:27 -06:00
Mauricio Siu fd199fdcc0 Merge pull request #2498 from Dokploy/2456-cannot-back-up-mariadb-database-access-denied-error
feat(database): enhance password validation for database schemas and …
2025-09-01 16:21:22 -06:00
Mauricio Siu 5e1a164a54 chore(pr-template): streamline checklist formatting and clarify issue closing instructions 2025-09-01 16:19:24 -06:00
Mauricio Siu bc2b4f1369 feat(database): enhance password validation for database schemas and update input components for password visibility 2025-09-01 16:16:55 -06:00
Tam Nguyen 38abe03257 fix(ui): flex-wrap on schedule name and enabled 2025-08-31 10:36:07 +10:00
autofix-ci[bot] 22e40134ea [autofix.ci] apply automated fixes 2025-08-31 00:30:08 +00:00
Tam Nguyen a2841fdd30 fix(ui): flex-wrap for cron and shell type 2025-08-31 10:27:12 +10:00
Tam Nguyen 468feaa092 fix(ui): improve server schedule responsiveness for mobile 2025-08-31 10:25:09 +10:00
Typed SIGTERM caf244120c fix: print error when docker build fails 2025-08-30 13:41:40 +08:00
Mauricio Siu 7273c636a0 Merge pull request #2461 from Dokploy/fix/re-apply-database-migration-fix
Reapply "refactor: update database connection handling and remove unu…
2025-08-28 19:21:28 -06:00
Mauricio Siu b9a8b27441 feat(notification): add 'ntfy' notification type and create associated table; update notification schema 2025-08-28 19:09:58 -06:00
Mauricio Siu 9f1f13b21b Merge branch 'canary' into feat/ntfy 2025-08-28 19:07:53 -06:00
Mauricio Siu 793a8ba760 chore: remove unused SQL file and related journal entry for 'flimsy_doctor_octopus' 2025-08-28 19:07:44 -06:00
Mauricio Siu d6a0585bae chore(package): update dokploy version to v0.25.0 2025-08-28 19:03:37 -06:00
Mauricio Siu 935d1686f2 chore: add new branch for database migration fix in Dokploy workflow 2025-08-28 19:02:21 -06:00
Mauricio Siu 349248105a Merge pull request #2482 from Dokploy/2470-post-rediscreate-returns-true-instead-of-the-redis-payload
fix(redis): return newRedis object instead of true in redis router
2025-08-28 18:43:04 -06:00
Mauricio Siu d922568510 fix(redis): return newRedis object instead of true in redis router 2025-08-28 18:42:21 -06:00
Mauricio Siu 44ae4df151 fix(settings): change user subscription query to protected procedure 2025-08-28 18:27:47 -06:00
Mauricio Siu 77fdda4c09 Merge pull request #2481 from Dokploy/feat/allow-chatwoot-on-paid-users
feat(settings): add user subscription check to dashboard layout
2025-08-28 18:27:05 -06:00
Mauricio Siu 8a1e36cc3b feat(settings): add user subscription check to dashboard layout 2025-08-28 18:26:05 -06:00
Mauricio Siu 1635bab44f Reapply "refactor: update database connection handling and remove unused migra…"
This reverts commit 17f333ac2a.
2025-08-24 23:49:48 -06:00
Mauricio Siu 4a52459015 Merge pull request #2460 from Dokploy/revert-2459-2234-database-migration-fails-with-password-authentication-failed-when-using-a-custom-postgres_password
Revert "refactor: update database connection handling and remove unused migra…"
2025-08-24 23:44:23 -06:00
Mauricio Siu 17f333ac2a Revert "refactor: update database connection handling and remove unused migra…" 2025-08-24 23:44:00 -06:00
Mauricio Siu d770307d64 Merge pull request #2459 from Dokploy/2234-database-migration-fails-with-password-authentication-failed-when-using-a-custom-postgres_password
refactor: update database connection handling and remove unused migra…
2025-08-24 23:43:52 -06:00
Mauricio Siu aa434cbdea feat(db): add database connection setup using drizzle-orm for PostgreSQL 2025-08-24 16:25:04 -06:00
Mauricio Siu c42054b965 feat(migration): implement database migration functionality using drizzle-orm 2025-08-24 16:22:42 -06:00
Mauricio Siu 03588bf375 chore: remove console.log statement from esbuild configuration 2025-08-24 16:21:01 -06:00
Mauricio Siu 8c420ff4f5 refactor: update package.json to use TypeScript source files instead of compiled JavaScript 2025-08-24 16:20:32 -06:00
Mauricio Siu cbf6f95891 refactor: update database connection handling and remove unused migration and seed files 2025-08-24 16:19:33 -06:00
Mauricio Siu 2d2a3d74ec Merge pull request #2412 from moosti/feat/two-factor-autofocus
feat: add autofocus to two-factor authentication input
2025-08-24 13:10:30 -06:00
Mauricio Siu 56b9fb531a Merge pull request #2447 from divaltor/volume-backup
feat(volume): Add possibility to keep latest N backups for custom apps
2025-08-24 00:44:27 -06:00
Mauricio Siu 59aaa1a47a fix(ui): adjust max width for volume backup dialog based on backup type 2025-08-24 00:40:17 -06:00
autofix-ci[bot] 5e4444610c [autofix.ci] apply automated fixes 2025-08-24 06:33:36 +00:00
Mauricio Siu 34e6cd87df Merge pull request #2410 from gentslava/fix/ollama-ai-provider
Ollama AI provider
2025-08-24 00:30:59 -06:00
Mauricio Siu 31b13b8d34 Merge pull request #2453 from Dokploy/2452-no-removal-of-preview-deployments-when-they-are-merged
fix: correct application not found error message and improve error ha…
2025-08-23 23:01:03 -06:00
Mauricio Siu 746cf76cf3 fix: correct application not found error message and improve error handling in removePreviewDeployment function 2025-08-23 22:59:52 -06:00
Mauricio Siu 46c53a05bf Merge pull request #2231 from PiquelChips/feat/label-previews
feat: preview deployments for pull requests with specific labels
2025-08-23 20:19:50 -06:00
Mauricio Siu f97f6d8178 Merge branch 'feat/label-previews' of github.com:PiquelChips/dokploy into feat/label-previews 2025-08-23 20:19:34 -06:00
Mauricio Siu c653dd604f feat: add previewLabels property to baseApp in drop and traefik test files 2025-08-23 20:19:14 -06:00
autofix-ci[bot] 40877e4370 [autofix.ci] apply automated fixes 2025-08-24 02:16:35 +00:00
Mauricio Siu 65203036f2 Merge branch 'canary' into feat/label-previews 2025-08-23 20:15:37 -06:00
Mauricio Siu 2ef5f967a9 refactor: clean up imports in show-preview-settings component 2025-08-23 20:14:41 -06:00
Mauricio Siu b20c95ffbc Merge branch 'canary' into feat/label-previews 2025-08-23 20:14:16 -06:00
Mauricio Siu 09b2492585 Merge branch 'feat/label-previews' of github.com:PiquelChips/dokploy into feat/label-previews 2025-08-23 20:13:22 -06:00
Mauricio Siu ca1fa7c4f7 feat: add support for preview labels in deployment process 2025-08-23 20:11:18 -06:00
autofix-ci[bot] 112b898d98 [autofix.ci] apply automated fixes 2025-08-24 02:01:00 +00:00
Mauricio Siu 8185482bcd Merge pull request #2370 from gentslava/fix/traefik_3
bump: Traefik 3.5.0
2025-08-23 19:53:47 -06:00
Mauricio Siu dd8f5dba09 Merge branch 'canary' into fix/traefik_3 2025-08-23 19:53:40 -06:00
Mauricio Siu e72a468c7e Merge pull request #2111 from Marukome0743/traefik
feat: bump Traefik v3.2.2 and add swarm network label
2025-08-23 19:50:50 -06:00
Mauricio Siu 02dd793dfb Merge pull request #2396 from alexevladgabriel/feat/self-env-refs
feat: Self reference env variables
2025-08-23 19:38:34 -06:00
Mauricio Siu 64ef033950 Merge pull request #2418 from periakteon/canary
fix(organization): integrate active organization refetching on update/create
2025-08-23 19:32:45 -06:00
Mauricio Siu 32f7bdf398 Merge pull request #2450 from Dokploy/2403-no-delete-volumes-option-when-deleting-in-bulk
feat(ui): add bulk deploy functionality for services in project dashb…
2025-08-23 16:59:03 -06:00
Mauricio Siu 8d73b77a19 Merge branch 'canary' into 2403-no-delete-volumes-option-when-deleting-in-bulk 2025-08-23 16:08:15 -06:00
Mauricio Siu 2e3d4f1021 feat(ui): implement bulk delete dialog for services in project dashboard 2025-08-23 16:06:25 -06:00
Mauricio Siu ba1f4dbd3a feat(ui): add bulk deploy functionality for services in project dashboard 2025-08-23 16:04:13 -06:00
Mauricio Siu 653beac3d9 feat(ui): implement bulk delete dialog for services with volume deletion option 2025-08-23 15:55:56 -06:00
Vlad Vladov 37c34fdadc feat(volume): Add possibility to keep latest N backups for custom containers 2025-08-23 18:07:45 +03:00
Paul Sommer d52fe5c050 fix: typo in ntfy provider 2025-08-21 15:57:20 +02:00
Paul Sommer 36281cd5d3 feat(notification): add ntfy notifications 2025-08-20 20:23:44 +02:00
Masum Gökyüz 69d676178f feat(organization): integrate active organization refetching on update/create 2025-08-20 09:33:01 +03:00
Vyacheslav Scherbinin 6612c92b4f chore: update ai providers 2025-08-20 13:16:04 +07:00
Vyacheslav Scherbinin 88c8fe4614 chore: update ollama ai provider 2025-08-20 00:58:39 +07:00
Vyacheslav Scherbinin 623fc26de5 fix(ai-ui): hide api key field for ollama 2025-08-19 23:56:54 +07:00
Vyacheslav Scherbinin 220576fd63 fix(ai-ui): empty models list text 2025-08-19 23:56:54 +07:00
Vyacheslav Scherbinin 07c23292da fix(ai): ollama fetch models 2025-08-19 23:56:54 +07:00
Vyacheslav Scherbinin 72fca80047 fix(ai-ui): disable AI key autocomplete 2025-08-19 23:56:54 +07:00
Vyacheslav Scherbinin 1e7f614bb6 fix(ai): ollama provider url-based detection 2025-08-19 23:56:53 +07:00
Vyacheslav Scherbinin e2662a0ec5 fix(ai): ollama ai provider api url 2025-08-19 23:56:46 +07:00
ispareh c96c25ca9f feat: add autofocus to two-factor authentication input 2025-08-19 14:40:04 +03:30
Marukome0743 4afd2d11fa feat: bump traefik to v3.2.2 2025-08-19 18:57:03 +09:00
Mauricio Siu ff20bb2731 chore(package): bump version to v0.24.12 2025-08-19 00:31:53 -06:00
Mauricio Siu 8a802b0739 Merge pull request #2402 from gentslava/fix/scroll-gutters
fix(ui): scroll gutters stable
2025-08-19 00:21:19 -06:00
Mauricio Siu e511173283 Merge pull request #2404 from gentslava/fix/modal-popover-handle
fix(ui): modal popover handle close
2025-08-19 00:19:44 -06:00
Mauricio Siu 763b1a344a Merge pull request #2407 from Dokploy/feat/prevent-delete-service-while-build-is-running
feat(ui): add alert blocks for running services in delete confirmatio…
2025-08-19 00:16:01 -06:00
Mauricio Siu a4041185f1 feat(ui): add alert blocks for running services in delete confirmation dialogs 2025-08-19 00:14:50 -06:00
Vyacheslav Scherbinin 522dcd6c08 fix(ui): modal pointer events predefine 2025-08-18 23:52:30 +07:00
Vyacheslav Scherbinin b4d5935875 fix(ui): modal popover handle close 2025-08-18 23:27:49 +07:00
Scai 8cc054389a feat: add self reference for env variables 2025-08-18 02:04:23 +03:00
Mauricio Siu 58b78e1ee3 fix(api): set deployment retries to 0 in Inngest function configuration 2025-08-17 13:44:09 -06:00
Mauricio Siu 5b0a8fde9c Merge pull request #2392 from Dokploy/2341-deployment-functionality-is-totally-broken-on-cloud-version
Integrate Inngest for deployment management in the API. Added Inngest…
2025-08-17 12:07:59 -06:00
Mauricio Siu 7e641c0ed5 Merge pull request #2394 from gentslava/fix/modal-content-overflow
fix(ui): modal double scroll
2025-08-17 12:03:59 -06:00
Mauricio Siu 83531ceacd Integrate Inngest for deployment management in the API. Added Inngest client initialization and updated deployment handling to send events to Inngest instead of using Redis. Updated package dependencies to include Inngest version 3.40.1. 2025-08-16 23:44:45 -06:00
Vyacheslav Scherbinin 6d9a1db8af fix(ui): scrollbar gutter stable 2025-08-17 12:36:40 +07:00
Vyacheslav Scherbinin e3979d2c48 fix(ui): fixed viewport 2025-08-17 12:09:26 +07:00
Vyacheslav Scherbinin 5f39dfee3a fix(ui): fix double scroll 2025-08-17 11:58:10 +07:00
Mauricio Siu 774365c68e Refactor and update various components in the Dokploy application, enhancing functionality and fixing minor issues across multiple pages and features, including dashboard, settings, and API integrations. 2025-08-16 20:18:08 -06:00
Mauricio Siu 0cdacb5501 update(package): bump version to v0.24.11 2025-08-16 19:50:15 -06:00
Mauricio Siu e266b1a620 Merge pull request #2361 from rennokki/fix/gitlab-url-basic-auth-support
fix: Added support for Basic Auth present in the GitLab URLs
2025-08-16 19:43:15 -06:00
autofix-ci[bot] 81ab71ba23 [autofix.ci] apply automated fixes 2025-08-17 01:33:47 +00:00
Mauricio Siu ae92725554 Merge pull request #2373 from gentslava/fix/dialog-backdrop-logic
Fix Dialog backdrop
2025-08-16 16:15:32 -06:00
Mauricio Siu 974d1d8b26 Merge pull request #2363 from bobbymannino/keyboard-nav-on-more-pages
add keyboard nav for compose/database pages
2025-08-16 16:14:10 -06:00
Mauricio Siu 7367601e26 Merge pull request #2383 from haouarihk/trim-ip-address
Trim ip address
2025-08-16 16:12:35 -06:00
Mauricio Siu 861b390707 Merge pull request #2385 from cheetahbyte/fix/template-search
fix(template): duplicate key issue causing wrong formatting in template search
2025-08-16 16:12:00 -06:00
Leonhard Breuer 4e7378371d fix(template): make every key in map unique using index
FIXES #2382
2025-08-15 19:52:55 +02:00
Haouari haitam Kouider b6cbf9127d trim ip address 2025-08-15 15:14:26 +00:00
Haouari haitam Kouider 661c517dfc trim the ip address 2025-08-15 15:13:24 +00:00
autofix-ci[bot] f3ec01ec77 [autofix.ci] apply automated fixes 2025-08-13 06:46:05 +00:00
Vyacheslav Scherbinin f4499463fe fix(dialog-ux): internal component state control 2025-08-13 12:54:58 +07:00
Vyacheslav Scherbinin 6e069154ef fix(dialog-ux): prevent default Radix double event onInteractOutside 2025-08-13 12:51:44 +07:00
Vyacheslav Scherbinin 66e6b56053 refactor: remove overflow hidden cause of useless 2025-08-13 10:07:08 +07:00
Vyacheslav Scherbinin 6248abd88e fix(ui): pointer events transparent modal overlay 2025-08-13 10:05:16 +07:00
autofix-ci[bot] 2c591cbd03 [autofix.ci] apply automated fixes 2025-08-13 01:25:30 +00:00
Vyacheslav Scherbinin 3864c50deb bump: Traefik v3.5.0 2025-08-13 08:23:30 +07:00
autofix-ci[bot] 83e8c82c4a [autofix.ci] apply automated fixes 2025-08-12 17:16:10 +00:00
Bob Mannino a41137aacc reduce time waiting for second nav key to 1.5s 2025-08-12 18:15:35 +01:00
Bob Mannino 213acd6287 fix: do not navigate if typing in input/textarea/select
fixes #2367
2025-08-12 18:15:18 +01:00
autofix-ci[bot] 0781336b8f [autofix.ci] apply automated fixes 2025-08-11 18:33:47 +00:00
Bob Mannino 28811ca66d add mariadb/mongodb keyboard shortcuts 2025-08-11 19:33:25 +01:00
autofix-ci[bot] ed53bdd0fa [autofix.ci] apply automated fixes 2025-08-11 18:28:50 +00:00
Bob Mannino 957d1b5966 add mysql keyboard shortcuts 2025-08-11 19:28:29 +01:00
Bob Mannino ad359defae format 2025-08-11 19:15:54 +01:00
Bob Mannino a4bbcea282 add keyboard shortcuts for compose/redis/postgres pages 2025-08-11 19:15:00 +01:00
PiquelChips 15e62961e8 fix: would only create previews if none of the labels were present 2025-08-11 14:09:02 +02:00
PiquelChips 429c1e4cd8 feat: better UI for submitting labels 2025-08-11 14:03:30 +02:00
Piquel 1904a3d1e9 Merge branch 'canary' into feat/label-previews 2025-08-11 13:29:04 +02:00
Alex Renoki f0278f354b Added support for Basic Auth present in the GitLab URLs 2025-08-11 09:56:49 +03:00
Mauricio Siu 9763dce045 fix(swarm): adjust validation for containerId to allow empty array 2025-08-10 23:26:20 -06:00
Mauricio Siu ef6dcaf363 chore(package): bump version to v0.24.10 2025-08-10 23:23:15 -06:00
Mauricio Siu 37b056cd4b Merge pull request #2314 from JamBalaya56562/strategy
ci(pull-request): use strategy matrix
2025-08-10 16:51:05 -06:00
Mauricio Siu 8bbef02e39 Merge pull request #2357 from Dokploy/1857-support-isolated-deployment-randomized-compose-for-compose-deployment-using-a-git-provider
1857 support isolated deployment randomized compose for compose deployment using a git provider
2025-08-10 16:44:28 -06:00
Mauricio Siu 231b8ed19d remove: eliminate Docker volumes from isolated deployment resources list 2025-08-10 16:43:03 -06:00
Mauricio Siu cfa0135932 remove: delete IsolatedDeployment component from dashboard 2025-08-10 16:42:50 -06:00
Mauricio Siu 85bce827eb fix(keyboard-nav): ensure correct type for shortcut keys in navigation 2025-08-10 16:41:18 -06:00
Mauricio Siu 1fe12ba93e feat(isolation): add preview functionality for isolated deployment with loading state and dialog 2025-08-10 16:38:10 -06:00
Mauricio Siu 4b1146ab6b remove the "isWildcard" column from the "domain" table in the database schema 2025-08-10 15:58:15 -06:00
Mauricio Siu fe2f6f842b Merge pull request #2332 from depado/fix-traefik-init-setup
fix(setup): properly handle dokploy-traefik container absence
2025-08-10 15:33:25 -06:00
Mauricio Siu c3f29c2694 Merge pull request #2352 from Aeriit/fix-remote-traefik-env
fix(traefik): on setup support serverId as parameter and input
2025-08-10 15:04:26 -06:00
Mauricio Siu d5307cb5d6 fix(traefik): streamline serverId handling in writeTraefikSetup function 2025-08-10 15:03:41 -06:00
Mauricio Siu b99556b389 Merge pull request #2355 from bobbymannino/application-keyboard-navigation
feat: add keyboard shortcuts to application page
2025-08-10 15:01:04 -06:00
Bob Mannino 112a1dedec feat: add keyboard shortcuts to application page 2025-08-10 14:34:06 +01:00
Mauricio Siu edbdc01a1e chore(package): bump version to v0.24.9 2025-08-10 06:12:28 -06:00
Aeriit 883e1d1bfe fix(traefik): on setup support serverId as parameter and input 2025-08-09 11:05:37 -04:00
Mauricio Siu 33d6c2073b Merge pull request #2337 from A-D-E/fix/gitlab-branches-pagination
fix: use configured GitLab URL instead of hardcoded gitlab.com
2025-08-09 00:23:43 -06:00
Lucas Manchine 8ea64f9de1 testing changes 2025-08-06 14:55:30 -03:00
Lucas Manchine 825a1fc495 Merge branch 'canary' into feature/stop-grace-period-2227 2025-08-06 10:30:57 -03:00
A-D-E 33873ce1e9 fix: use configured GitLab URL instead of hardcoded gitlab.com
The previous pagination implementation accidentally hardcoded the GitLab URL
to gitlab.com, breaking the integration for self-hosted GitLab instances.

Changes:
- Replace hardcoded 'https://gitlab.com' with gitlabProvider.gitlabUrl
- Maintains the pagination functionality added in previous commit

Fixes: GitLab branches API calls failing for self-hosted instances
2025-08-05 21:31:15 +02:00
depado 1d94c85c2b fix(setup): properly handle dokploy-traefik container absence 2025-08-05 14:53:35 +02:00
Mauricio Siu 1758655f66 feat(swarm): add OpenAPI metadata for drop-deployment endpoint in swarm router 2025-08-04 00:28:03 -06:00
Mauricio Siu 029eed7755 refactor(dashboard): improve layout and spacing in ShowConvertedCompose component 2025-08-03 18:10:19 -06:00
Mauricio Siu f017536396 chore(package): bump version to v0.24.7 2025-08-03 18:04:24 -06:00
Mauricio Siu ba5505cf81 feat(auth): add logger configuration to disable logging in production environment 2025-08-03 18:04:09 -06:00
Mauricio Siu 5ff5da9ff9 Merge pull request #2321 from Dokploy/1738-database-placement-constraints
feat(cluster-settings): Add swarm settings for databases
2025-08-03 17:41:08 -06:00
Mauricio Siu e96a8ea4ad feat(databases): increment ForceUpdate in TaskTemplate for service updates across MariaDB, MongoDB, MySQL, Postgres, and Redis 2025-08-03 17:39:35 -06:00
Mauricio Siu 42864d2472 feat(docker): update generateConfigContainer to accept Partial<ApplicationNested> and enhance mount checks 2025-08-03 17:21:09 -06:00
Mauricio Siu ae25ea265c feat(databases): enhance database service configuration by integrating health checks, restart policies, and additional settings across MariaDB, MongoDB, MySQL, Postgres, and Redis 2025-08-03 16:52:43 -06:00
Mauricio Siu 0755f28307 refactor(cluster-settings): update dialog content and improve layout in swarm settings component 2025-08-03 16:45:07 -06:00
Mauricio Siu 3d10d48425 fix(cluster-settings): ensure data validation for registryId in form defaults 2025-08-03 16:40:24 -06:00
Mauricio Siu 6e79183f6a feat(cluster-settings): refactor cluster settings components to support multiple database types and add new swarm configuration fields 2025-08-03 16:36:14 -06:00
Mauricio Siu 2318fb062a Merge pull request #2320 from Dokploy/2004-how-to-pause-log-output-in-doploy
feat(docker-logs): add pause/resume functionality for log streaming
2025-08-03 15:45:54 -06:00
Mauricio Siu 14b4bc9d85 feat(docker-logs): add pause/resume functionality for log streaming with message buffering 2025-08-03 15:43:53 -06:00
Mauricio Siu 4c72f1894c Merge pull request #2319 from Dokploy/1614-sort-project-alphabetically-and-by-other-criteria
feat(show-projects): implement project sorting and filtering function…
2025-08-03 15:25:52 -06:00
Mauricio Siu ebd632df04 feat(show-projects): implement project sorting and filtering functionality 2025-08-03 15:24:56 -06:00
Mauricio Siu 4878ed2b6f Merge pull request #2318 from Dokploy/2223-local-deployment-doesnt-replace-old-container
feat(handle-ports): add publish mode warning for host mode limitations
2025-08-03 15:15:05 -06:00
Mauricio Siu 1d3ab2bafa feat(handle-ports): add publish mode warning for host mode limitations 2025-08-03 15:13:11 -06:00
Mauricio Siu 7cb7cfa2a8 feat(pr-template): add a pull request template to standardize submissions 2025-08-03 13:38:17 -06:00
Mauricio Siu 6009697710 Merge pull request #2312 from JamBalaya56562/create-pr
ci(create-pr): remove unused id
2025-08-03 13:30:29 -06:00
Mauricio Siu 6be86b49bb Merge pull request #2292 from JamBalaya56562/lint-shared
refactor: lint apps/components/shared files
2025-08-03 13:27:11 -06:00
Mauricio Siu 1e81244e0b docs(contributing): update pull request guidelines and clarify branch roles 2025-08-03 13:21:02 -06:00
Mauricio Siu f659ea463d Merge pull request #2311 from JamBalaya56562/contributing
docs(contributing): use Alerts syntax of GitHub markdown
2025-08-03 13:17:16 -06:00
JamBalaya56562 22c7c6e6fb ci(pull-request): use strategy matrix 2025-08-03 18:40:37 +09:00
JamBalaya56562 caf57276a4 ci(create-pr): remove unused id 2025-08-03 18:09:33 +09:00
JamBalaya56562 15c6c7e657 docs(contributing): use Alerts syntax of GitHub markdown 2025-08-03 17:50:29 +09:00
JamBalaya56562 8be0db385a refactor: lint apps/components/shared files 2025-08-03 17:25:06 +09:00
Mauricio Siu 4259e2533e Merge pull request #2309 from Dokploy/1778-railpack-frontend-version-is-hardcoded
1778 railpack frontend version is hardcoded
2025-08-03 02:22:40 -06:00
Mauricio Siu a138d12082 chore(tests): set railpackVersion to '0.2.2' in drop test configuration 2025-08-03 02:20:28 -06:00
Mauricio Siu a2405ddd84 chore(build): add cleanup step to remove builder container after Railpack build completion 2025-08-03 02:14:59 -06:00
Mauricio Siu e785ad5599 fix(build): set default railpackVersion to '0.2.2' and ensure cleanup in build process
- Updated the ShowBuildChooseForm component to default railpackVersion to '0.2.2' if not specified.
- Added cleanup step in the buildRailpack function to remove the builder container after execution.
- Refactored application router to include railpackVersion in the application schema.
2025-08-03 02:00:10 -06:00
Mauricio Siu cc6445a8ec feat(application): add railpackVersion field to application schema and update related components
- Introduced a new column `railpackVersion` in the application table with a default value of '0.2.2'.
- Updated the application form to include a field for `railpackVersion` when the build type is set to railpack.
- Adjusted the build process to utilize the specified `railpackVersion` dynamically.
- Enhanced validation schema to accommodate the new field.
2025-08-03 01:54:07 -06:00
Mauricio Siu b8f27d7b76 Merge pull request #2306 from Dokploy/2170-docker-compose-volume-mount-causes-oci-runtime-exec-failed-unless-container-is-force-recreated
refactor(docker): update docker exec command to set working directory…
2025-08-03 01:48:22 -06:00
Mauricio Siu 32f61b5e9b refactor(docker): update docker exec command to set working directory for terminal sessions 2025-08-03 01:47:31 -06:00
Mauricio Siu e2d6b5eb8a Merge pull request #2305 from Dokploy/2249-traefik-doesnt-reload-when-installed-as-a-docker-service
refactor(traefik): update Traefik initialization to support standalon…
2025-08-03 01:21:45 -06:00
Mauricio Siu 7413c9484a refactor(settings): remove unused getTraefikPorts function to streamline settings router 2025-08-03 01:19:14 -06:00
Mauricio Siu 607c505c4b refactor(traefik): update Traefik initialization to support standalone and service modes, enhance port handling with protocol specification 2025-08-03 01:18:18 -06:00
Mauricio Siu 42629e83a1 Merge pull request #2304 from Dokploy/1681-application-automated-build-fails-due-connection-http-basic-access-denied
refactor(gitea, gitlab): remove unused parameters and fetch entities …
2025-08-02 21:24:51 -06:00
Mauricio Siu b9f18cddf7 refactor(gitlab): reorder token refresh and provider fetching for improved clarity 2025-08-02 21:24:33 -06:00
Mauricio Siu 2790895642 refactor(gitea, gitlab): remove unused parameters and fetch entities by ID 2025-08-02 21:20:50 -06:00
Mauricio Siu 7b76bb93b3 Merge branch 'canary' into feature/stop-grace-period-2227 2025-08-02 19:37:24 -06:00
Mauricio Siu c21c88d89f chore(package): bump version from v0.24.5 to v0.24.6 2025-08-02 19:37:10 -06:00
Mauricio Siu bf6b9c6893 Merge pull request #2302 from Dokploy/2281-url-rewrite-via-domain-routing-not-functioning
refactor(domain): enhance middleware handling for Traefik routers and…
2025-08-02 19:32:33 -06:00
Mauricio Siu e08fe1dbea test(labels): add comprehensive tests for middleware handling in createDomainLabels function 2025-08-02 19:31:21 -06:00
Mauricio Siu 0b9eaac390 refactor(domain): enhance middleware handling for Traefik routers and improve path validation 2025-08-02 19:24:11 -06:00
Mauricio Siu 5ed49a5ca1 Merge pull request #2301 from Dokploy/2074-keep-latest-is-not-respected
fix(backups): change backup file extension from .dump.gz to .sql.gz f…
2025-08-02 18:41:11 -06:00
Mauricio Siu 1300a6242c fix(backups): change backup file extension from .dump.gz to .sql.gz for consistency 2025-08-02 18:40:40 -06:00
Mauricio Siu 201f07c084 fix(volumes): adjust layout for volume display and improve conditional rendering 2025-08-02 16:13:51 -06:00
Mauricio Siu c5161f1612 Merge pull request #2300 from Dokploy/2259-file-mounts-not-updating
feat(mount): refactor updateMount logic and add updateFileMount funct…
2025-08-02 16:08:15 -06:00
Mauricio Siu 0755de03c2 feat(mount): refactor updateMount logic and add updateFileMount function for handling file mounts 2025-08-02 16:06:46 -06:00
Mauricio Siu f376ea5fec Merge pull request #2297 from Dokploy/feat/add-custom-service-field-domains-compose
feat(dashboard): add manual input for service name in domains for docker compose
2025-08-02 13:21:01 -06:00
Mauricio Siu 346eb24926 feat(dashboard): add manual input option for service name selection in domain handling 2025-08-02 13:20:00 -06:00
Mauricio Siu fe45c69939 Merge pull request #2296 from Dokploy/2291-mise-is-trying-to-verify-the-gpg-signature-of-the-nodejs-binary-for-node22180-but-it-cant-find-the-public-key-used-to-verify-the-signature
chore: update Railpack version to 0.2.2 in Dockerfile and related scr…
2025-08-02 13:14:58 -06:00
Mauricio Siu 39d46a51b3 chore: update Railpack version to 0.2.2 in Dockerfile and related scripts 2025-08-02 13:08:46 -06:00
Mauricio Siu 3e193590cc Merge pull request #2295 from JamBalaya56562/readme
docs: polish `README.md`
2025-08-02 12:50:26 -06:00
Mauricio Siu c157a353f3 Merge pull request #2279 from A-D-E/fix/gitlab-branches-pagination
The getGitlabBranches function was only returning the first 20 branches
2025-08-02 12:48:32 -06:00
Mauricio Siu 2a14ae0c7f Merge branch 'canary' into fix/gitlab-branches-pagination 2025-08-02 12:44:14 -06:00
JamBalaya56562 144c74e7f7 docs: polish README.md 2025-08-02 20:38:28 +09:00
Mauricio Siu 1d4d766f3a Merge pull request #2229 from danielepintore/canary
feat(dashboard): generate user fallback avatar using user email and allow user to choose default avatar
2025-08-02 00:34:16 -06:00
Mauricio Siu 025d439f71 Merge branch 'canary' into feat/label-previews 2025-08-02 00:28:52 -06:00
Mauricio Siu 8532cba638 Merge pull request #2255 from Marukome0743/lint-layouts
refactor: lint apps/components/layouts files
2025-08-02 00:27:09 -06:00
Mauricio Siu fdb4b176cb Merge pull request #2254 from Marukome0743/lint-test
refactor: lint apps/docker/__test__ files
2025-08-02 00:27:00 -06:00
Mauricio Siu f2b214f8f0 Merge pull request #2266 from rainwashed/canary
fix: github app creation name conflicting with already existing Dokploy names
2025-08-02 00:23:15 -06:00
autofix-ci[bot] 0bcc59f90f [autofix.ci] apply automated fixes 2025-08-02 06:19:09 +00:00
Mauricio Siu 7ae4bf3215 Merge pull request #2290 from Dokploy/2277-two-copies-of-the-same-volume-backup-are-uploaded-to-s3
refactor(backup): consolidate utility imports and add local backup cl…
2025-08-01 01:28:17 -06:00
Mauricio Siu 0f5cf37757 refactor(backup): consolidate utility imports and add local backup cleanup after S3 upload 2025-08-01 01:27:35 -06:00
Mauricio Siu a7bde655da Merge pull request #2289 from Dokploy/2282-custom-compose-path-not-being-used-when-manually-restarting-service
refactor(compose): reorganize imports and simplify command execution …
2025-08-01 01:16:33 -06:00
Mauricio Siu 295b6df5e1 refactor(compose): reorganize imports and simplify command execution for starting Docker Compose 2025-08-01 01:15:29 -06:00
Mauricio Siu b5b63eae4f Merge pull request #2288 from Dokploy/git-fetch-origin-git-checkout-2263-swarm-containers-no-data-found
refactor(application): update application handling to support multipl…
2025-08-01 00:35:32 -06:00
Mauricio Siu 794e03460f refactor(application): update application handling to support multiple app names and improve data structure 2025-08-01 00:34:57 -06:00
A-D-E e8f36f8ba5 The getGitlabBranches function was only returning the first 20 branches
due to GitLab's default API pagination limit. This prevented users from
accessing branches in repositories with more than 20 branches.

Changes:
- Add pagination loop to fetch all branches across multiple pages
- Set per_page to 100 (GitLab's maximum) for efficiency
- Add safety check using x-total header to prevent unnecessary requests
- Follow the same pagination pattern as validateGitlabProvider function

Fixes issue where branch selection was limited to first 20 branches
in repositories with many branches.
2025-07-30 15:17:14 +02:00
Lucas Manchine 64290fcbf6 fix linter issues 2025-07-29 09:33:19 -03:00
Daniele Pintore f9210d3165 lint: formatted changes using biome 2025-07-28 23:39:06 +02:00
rainwashed 9bc6411c98 fix: github app creation name conflicting with already existing Dokploy-Time names
appended a 5-char random string to the name creation as to prevent
conflicts with other existing Dokploy GitHub apps.
2025-07-28 17:18:30 -04:00
Daniele Pintore f8261b5364 feat(dashboard): use username instead of email for the generation of the
fallback avatar image
2025-07-28 19:20:05 +02:00
Daniele Pintore 30c2c7afb0 feat(dashboard): generate user fallback avatar using user email. Allow
user to select the default avatar.
2025-07-28 16:17:52 +02:00
Marukome0743 f26c1c0da6 refactor: lint apps/docker/__test__ files 2025-07-28 20:32:08 +09:00
Marukome0743 d02976476a refactor: lint apps/components/layouts files 2025-07-28 19:56:44 +09:00
Mauricio Siu 17e9154887 Merge pull request #2257 from Dokploy/fix/send-build-error-on-remote-servers
Fix/send build error on remote servers
2025-07-28 01:52:57 -06:00
Mauricio Siu 2442494096 fix(application): simplify error message handling in deployment notifications 2025-07-28 01:51:21 -06:00
Mauricio Siu bac2afb423 refactor(application): exclude appName from updateApplication data to streamline database updates 2025-07-28 01:50:58 -06:00
Mauricio Siu 4e9630e976 Merge pull request #2256 from Dokploy/feat/enhancements-cloud-version-ui
feat(dashboard): enhance application and database forms with tooltips…
2025-07-28 01:50:26 -06:00
Mauricio Siu 558f6aecae fix(application): improve error handling and notification messages during deployment 2025-07-28 01:48:33 -06:00
autofix-ci[bot] 9baafb83ff [autofix.ci] apply automated fixes 2025-07-28 07:38:28 +00:00
Mauricio Siu c3e2b0d0f1 feat(dashboard): enhance application and database forms with tooltips for better user guidance 2025-07-28 01:12:43 -06:00
Mauricio Siu 11d584316a chore(package): bump version to v0.24.5 2025-07-28 00:57:44 -06:00
Mauricio Siu f78dc555b2 Merge pull request #2244 from jhon2c/feat/improve-server-ux
feat(ux): Improve UX Based on Community Feedback
2025-07-27 23:21:24 -06:00
Mauricio Siu 5812b12a59 Merge pull request #2236 from masesisaac/canary
fix(dashboard): Update app security view to hide password
2025-07-27 23:16:07 -06:00
Mauricio Siu 7301d15e8f Merge pull request #2230 from amustapha/patch-1
fix: wrap user prompt in ai modal to prevent text stretch
2025-07-27 23:15:01 -06:00
Mauricio Siu f79796a6c8 Merge pull request #2188 from Marukome0743/vscode
chore: add biome settings for vscode editor
2025-07-27 23:14:33 -06:00
Mauricio Siu 4122b37abd Merge pull request #2250 from Dokploy/feat/add-name-field-to-profile
feat(profile): add optional name field to user profile form and schema
2025-07-27 23:13:26 -06:00
Mauricio Siu 79e9593663 feat(profile): add optional name field to user profile form and schema 2025-07-27 23:13:06 -06:00
masesisaac def3fa0030 fix(security): change password input type to 'password' 2025-07-28 04:58:43 +03:00
autofix-ci[bot] d561068bcd [autofix.ci] apply automated fixes 2025-07-26 17:26:20 +00:00
Jhon 212c1b2d5f feat(dashboard): show "Action Required" badge for incomplete Git provider setup 2025-07-26 14:18:26 -03:00
Jhon d3a54172b5 feat(ux): add conditional server selection functionality to application forms 2025-07-26 13:53:28 -03:00
PiquelChips 1f9ef473f1 format some files 2025-07-24 19:45:43 +02:00
PiquelChips a0bbf7be23 add check for presence of labels 2025-07-24 19:35:33 +02:00
PiquelChips a5bc384d77 run database migration 2025-07-24 19:02:50 +02:00
masesisaac cda33eb291 refactor(dashboard): reorder imports in show-security.tsx for consistency 2025-07-24 17:45:26 +03:00
masesisaac c178234e53 fix(dashboard): hide basic auth password by default 2025-07-24 17:41:51 +03:00
Lucas Manchine 4f2b270f1d improved form 2025-07-23 18:32:42 -03:00
Lucas Manchine e22489926b feat: Add stop_grace_period to swarm settings, test layout 2025-07-23 21:18:57 +00:00
Lucas Manchine b4a5221caf feat: Add stop_grace_period to swarm settings 2025-07-23 20:38:27 +00:00
PiquelChips f2ae39aa86 feat: preview deployments for pull requests with specific labels 2025-07-23 21:39:54 +02:00
Abdulhakeem Adetunji Mustapha 329db1fd1a fix: wrap user prompt in ai modal to prevent text stretch 2025-07-23 19:30:47 +01:00
Marukome0743 6efbf030a7 chore: add biome settings for vscode editor 2025-07-23 08:49:59 +09:00
Mauricio Siu b95dfed8fc chore(package): bump version to v0.24.4 2025-07-20 20:06:47 -06:00
Mauricio Siu 7fe3418d55 Merge pull request #2218 from Dokploy/2179-reloading-traefik-on-the-remote-server-will-cause-traefik-on-the-instance-to-change-accordingly
fix(traefik): remove duplicate file write operation in writeTraefikCo…
2025-07-20 20:05:48 -06:00
Mauricio Siu 288d86c73b fix(traefik): remove duplicate file write operation in writeTraefikConfigInPath function 2025-07-20 20:05:30 -06:00
Mauricio Siu ffd5ccd386 Merge pull request #2202 from gentslava/feat/traefik-config
feat(config): Traefik
2025-07-20 19:45:53 -06:00
Mauricio Siu 98ddd096e5 Update packages/server/src/setup/traefik-setup.ts 2025-07-20 19:45:41 -06:00
Mauricio Siu da6cc9fe72 Merge pull request #2190 from Marukome0743/format
chore: version up format.yml actions
2025-07-20 19:44:20 -06:00
Mauricio Siu 22d0af269e Merge pull request #2200 from Marukome0743/server
refactor: lint and sort imports on dokploy/server
2025-07-20 19:42:15 -06:00
Mauricio Siu f0fdc46de5 Merge pull request #2187 from Marukome0743/v2
chore: upgrade to Biome v2
2025-07-20 19:41:49 -06:00
Mauricio Siu 9aea24115d Merge pull request #2199 from Marukome0743/lint
refactor: lint and sort import on dokploy application
2025-07-20 19:41:02 -06:00
Mauricio Siu a9ee6c2393 Merge pull request #2194 from Marukome0743/pnpm
chore(package): version up pnpm to v9.12.0
2025-07-20 19:40:09 -06:00
Mauricio Siu 349717044c Merge pull request #2196 from Marukome0743/dispatch
ci: remove custom branch and add workflow_dispatch event
2025-07-20 19:37:27 -06:00
Mauricio Siu f94f32695f Merge pull request #2195 from Marukome0743/monitoring
chore: remove `apps/monitoring` from `pnpm-workspace.yaml`
2025-07-20 19:37:07 -06:00
Mauricio Siu 37b78ea09c Merge pull request #2217 from Dokploy/2201-daily-docker-cleanup-not-working-on-remote-server
fix(dashboard): update Docker cleanup toggle logic to prioritize serv…
2025-07-20 19:01:46 -06:00
Mauricio Siu 9b89b4631f fix(dashboard): update Docker cleanup toggle logic to prioritize server settings 2025-07-20 19:01:20 -06:00
Mauricio Siu 7100095f2b Merge pull request #2216 from Dokploy/2209-update-s3-destination-form-loses-its-state-when-current-tab-loses-its-focus
fix(dashboard): disable refetch on window focus for destination handling
2025-07-20 18:57:33 -06:00
Mauricio Siu a36ab65aa6 fix(dashboard): disable refetch on window focus for destination handling 2025-07-20 18:56:35 -06:00
Mauricio Siu bf81ba20ff Merge pull request #2215 from Dokploy/2197-git-provider-api-undefined_value-error
refactor(auth): simplify user session structure in validateRequest fu…
2025-07-20 18:55:16 -06:00
Mauricio Siu 658a4a9b99 refactor(auth): simplify user session structure in validateRequest function
- Changed user object in mockSession to only include userId, removing email and name for a more streamlined session representation.
2025-07-20 18:54:57 -06:00
Mauricio Siu 47cb096cf3 Merge pull request #2214 from Dokploy/2203-identical-webhook-redeploy-url-after-duplicating-project
feat(project): add refreshToken to application and compose data retri…
2025-07-20 18:45:39 -06:00
Mauricio Siu f3856722da feat(project): add refreshToken to application and compose data retrieval
- Included refreshToken in the data returned from findApplicationById and findComposeById functions to enhance application state management.
2025-07-20 18:45:18 -06:00
Vyacheslav Scherbinin a67c3eb979 feat(conf): accessLog filePath 2025-07-16 16:46:47 +07:00
Vyacheslav Scherbinin aaa205f104 feat(conf): disable sendAnonymousUsage 2025-07-16 12:29:31 +07:00
Marukome0743 cadea7ff28 refactor: lint and sort imports on dokploy/server 2025-07-15 14:22:37 +09:00
Marukome0743 9ab937f726 refactor: lint dokploy application 2025-07-15 14:13:32 +09:00
Marukome0743 d0af517eb7 ci: remove custom branch and add workflow_dispatch event 2025-07-14 19:03:41 +09:00
Marukome0743 66bdf9bf0a chore: remove apps/monitoring from pnpm-workspace.yaml 2025-07-14 18:24:26 +09:00
Marukome0743 d4a3af475a chore(package): version up pnpm to v9.12.0 2025-07-14 15:58:20 +09:00
autofix-ci[bot] e92a8d7c98 [autofix.ci] apply automated fixes 2025-07-14 15:30:24 +09:00
Marukome0743 c4fec8cee5 chore: upgrade to Biome v2 2025-07-14 15:30:23 +09:00
Marukome0743 55f75bce53 chore: version up format.yml actions 2025-07-14 15:30:06 +09:00
Mauricio Siu fdc524d79d fix(ui): adjust layout in UpdateServer component
- Removed unnecessary padding from DialogContent for a cleaner appearance.
- Added margin-top to the button container for improved spacing.
2025-07-13 23:37:05 -06:00
Mauricio Siu 93d6662466 docs(preview): update collaborator permission description in preview settings 2025-07-13 23:26:41 -06:00
Mauricio Siu 1977235d31 Merge pull request #2192 from Dokploy/fix/preview-deployments-public-repos
feat(preview): add collaborator permission requirement for preview de…
2025-07-13 23:20:51 -06:00
Mauricio Siu 1dd713a1d1 fix(deploy): change preview deployment limit check to be exclusive 2025-07-13 23:20:23 -06:00
Mauricio Siu 18b65f28f2 chore(package): bump version to v0.24.3 and comment out unused trustedOrigins function in auth.ts 2025-07-13 23:19:31 -06:00
Mauricio Siu 666db23b8e test: add previewRequireCollaboratorPermissions field to drop and traefik test cases 2025-07-13 23:17:32 -06:00
Mauricio Siu 2ca5321fdc feat(preview): add collaborator permission requirement for preview deployments
- Introduced a new boolean field `previewRequireCollaboratorPermissions` in the application schema to enforce permission checks for preview deployments.
- Updated the UI to include a toggle for this setting in the preview deployment settings.
- Enhanced GitHub deployment handler to validate PR authors against the required permissions, blocking unauthorized deployments and providing security notifications.
- Added SQL migration to update the database schema accordingly.
2025-07-13 23:12:09 -06:00
550 changed files with 106954 additions and 7220 deletions
+18
View File
@@ -0,0 +1,18 @@
## What is this PR about?
Please describe in a short paragraph what this PR is about.
## Checklist
Before submitting this PR, please make sure that:
- [] You created a dedicated branch based on the `canary` branch.
- [] You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
- [] You have tested this PR in your local instance.
## Issues related (if applicable)
closes #123
## Screenshots (if applicable)
Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

-4
View File
@@ -19,17 +19,14 @@ jobs:
fetch-depth: 0
- name: Get version from package.json
id: package_version
run: echo "VERSION=$(jq -r .version ./apps/dokploy/package.json)" >> $GITHUB_ENV
- name: Get latest GitHub tag
id: latest_tag
run: |
LATEST_TAG=$(git ls-remote --tags origin | awk -F'/' '{print $3}' | sort -V | tail -n1)
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
echo $LATEST_TAG
- name: Compare versions
id: compare_versions
run: |
if [ "${{ env.VERSION }}" != "${{ env.LATEST_TAG }}" ]; then
VERSION_CHANGED="true"
@@ -42,7 +39,6 @@ jobs:
echo "Latest tag: ${{ env.LATEST_TAG }}"
echo "Version changed: $VERSION_CHANGED"
- name: Check if a PR already exists
id: check_pr
run: |
PR_EXISTS=$(gh pr list --state open --base main --head canary --json number --jq '. | length')
echo "PR_EXISTS=$PR_EXISTS" >> $GITHUB_ENV
+2 -1
View File
@@ -2,7 +2,8 @@ name: Build Docker images
on:
push:
branches: ["canary", "main", "feat/monitoring"]
branches: [main, canary]
workflow_dispatch:
jobs:
build-and-push-cloud-image:
+2 -1
View File
@@ -2,7 +2,8 @@ name: Dokploy Docker Build
on:
push:
branches: [main, canary, "1061-custom-docker-service-hostname"]
branches: [main, canary, "fix/re-apply-database-migration-fix"]
workflow_dispatch:
env:
IMAGE_NAME: dokploy/dokploy
+3 -3
View File
@@ -11,12 +11,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup biomeJs
uses: biomejs/setup-biome@v2
- name: Run Biome formatter
run: biome format . --write
run: biome format --write
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 # v1.3.2
+9 -30
View File
@@ -4,9 +4,15 @@ on:
pull_request:
branches: [main, canary]
permissions:
contents: read
jobs:
lint-and-typecheck:
pr-check:
runs-on: ubuntu-latest
strategy:
matrix:
job: [build, test, typecheck]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
@@ -15,32 +21,5 @@ jobs:
node-version: 20.16.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build
- run: pnpm typecheck
build-and-test:
needs: lint-and-typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20.16.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build
- run: pnpm build
parallel-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20.16.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build
- run: pnpm test
- run: pnpm server:build
- run: pnpm ${{ matrix.job }}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["biomejs.biome"]
}
+8
View File
@@ -0,0 +1,8 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
}
}
+11 -4
View File
@@ -87,7 +87,8 @@ pnpm run dokploy:dev
Go to http://localhost:3000 to see the development server
Note: this project uses Biome. If your editor is configured to use another formatter such as Prettier, it's recommended to either change it to use Biome or turn it off.
> [!NOTE]
> This project uses Biome. If your editor is configured to use another formatter such as Prettier, it's recommended to either change it to use Biome or turn it off.
## Build
@@ -117,10 +118,10 @@ In the case you lost your password, you can reset it using the following command
pnpm run reset-password
```
If you want to test the webhooks on development mode using localtunnel, make sure to install `localtunnel`
If you want to test the webhooks on development mode using localtunnel, make sure to install [`localtunnel`](https://localtunnel.app/)
```bash
bunx lt --port 3000
pnpm dlx localtunnel --port 3000
```
If you run into permission issues of docker run the following command
@@ -152,7 +153,7 @@ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.
## Pull Request
- The `main` branch is the source of truth and should always reflect the latest stable release.
- The `canary` branch is the source of truth and should always reflect the latest stable release.
- Create a new branch for each feature or bug fix.
- Make sure to add tests for your changes.
- Make sure to update the documentation for any changes Go to the [docs.dokploy.com](https://docs.dokploy.com) website to see the changes.
@@ -161,6 +162,12 @@ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.
- If your pull request fixes an open issue, please reference the issue in the pull request description.
- Once your pull request is merged, you will be automatically added as a contributor to the project.
**Important Considerations for Pull Requests:**
- **Focus and Scope:** Each Pull Request should ideally address a single, well-defined problem or introduce one new feature. This greatly facilitates review and reduces the chances of introducing unintended side effects.
- **Avoid Unfocused Changes:** Please avoid submitting Pull Requests that contain only minor changes such as whitespace adjustments, IDE-generated formatting, or removal of unused variables, unless these are part of a larger, clearly defined refactor or a dedicated "cleanup" Pull Request that addresses a specific `good first issue` or maintenance task.
- **Issue Association:** For any significant change, it's highly recommended to open an issue first to discuss the proposed solution with the community and maintainers. This ensures alignment and avoids duplicated effort. If your PR resolves an existing issue, please link it in the description (e.g., `Fixes #123`, `Closes #456`).
Thank you for your contribution!
## Templates
+1 -1
View File
@@ -58,7 +58,7 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
&& pnpm install -g tsx
# Install Railpack
ARG RAILPACK_VERSION=0.0.64
ARG RAILPACK_VERSION=0.2.2
RUN curl -sSL https://railpack.com/install.sh | bash
# Install buildpacks
+23 -7
View File
@@ -1,6 +1,6 @@
<div align="center">
<a href="https://dokploy.com">
<img src=".github/sponsors/logo.png" alt="Dokploy - Open Source Alternative to Vercel, Heroku and Netlify." align="center" width="100%" />
<img src=".github/sponsors/logo.png" alt="Dokploy - Open Source Alternative to Vercel, Heroku and Netlify." width="100%" />
</a>
</br>
</br>
@@ -11,9 +11,26 @@
</div>
<br />
<div align="center" markdown="1">
<sup>Special thanks to:</sup>
<br>
<br>
<a href="https://tuple.app/dokploy">
<img src=".github/sponsors/tuple.png" alt="Tuple's sponsorship image" width="400"/>
</a>
### [Tuple, the premier screen sharing app for developers](https://tuple.app/dokploy)
[Available for MacOS & Windows](https://tuple.app/dokploy)<br>
</div>
Dokploy is a free, self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
### Features
## ✨ Features
Dokploy includes multiple features to make your life easier.
@@ -43,7 +60,7 @@ curl -sSL https://dokploy.com/install.sh | sh
For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
## Sponsors
## ♥️ Sponsors
🙏 We're deeply grateful to all our sponsors who make Dokploy possible! Your support helps cover the costs of hosting, testing, and developing new features.
@@ -95,7 +112,6 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
### Community Backers 🤝
#### Organizations:
[Sponsors on Open Collective](https://opencollective.com/dokploy)
@@ -107,15 +123,15 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
### Contributors 🤝
<a href="https://github.com/dokploy/dokploy/graphs/contributors">
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" />
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" alt="Contributors" />
</a>
## Video Tutorial
## 📺 Video Tutorial
<a href="https://youtu.be/mznYKPvhcfw">
<img src="https://dokploy.com/banner.png" alt="Watch the video" width="400"/>
</a>
## Contributing
## 🤝 Contributing
Check out the [Contributing Guide](CONTRIBUTING.md) for more information.
+6 -1
View File
@@ -9,6 +9,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"inngest": "3.40.1",
"@dokploy/server": "workspace:*",
"@hono/node-server": "^1.14.3",
"@hono/zod-validator": "0.3.0",
@@ -29,5 +30,9 @@
"tsx": "^4.16.2",
"typescript": "^5.8.3"
},
"packageManager": "pnpm@9.5.0"
"packageManager": "pnpm@9.12.0",
"engines": {
"node": "^20.16.0",
"pnpm": ">=9.12.0"
}
}
+159 -29
View File
@@ -2,21 +2,90 @@ import { serve } from "@hono/node-server";
import { Hono } from "hono";
import "dotenv/config";
import { zValidator } from "@hono/zod-validator";
import { Queue } from "@nerimity/mimiqueue";
import { createClient } from "redis";
import { Inngest } from "inngest";
import { serve as serveInngest } from "inngest/hono";
import { logger } from "./logger.js";
import { type DeployJob, deployJobSchema } from "./schema.js";
import {
cancelDeploymentSchema,
type DeployJob,
deployJobSchema,
} from "./schema.js";
import { deploy } from "./utils.js";
const app = new Hono();
const redisClient = createClient({
url: process.env.REDIS_URL,
// Initialize Inngest client
export const inngest = new Inngest({
id: "dokploy-deployments",
name: "Dokploy Deployment Service",
});
export const deploymentFunction = inngest.createFunction(
{
id: "deploy-application",
name: "Deploy Application",
concurrency: [
{
key: "event.data.serverId",
limit: 1,
},
],
retries: 0,
cancelOn: [
{
event: "deployment/cancelled",
if: "async.data.applicationId == event.data.applicationId || async.data.composeId == event.data.composeId",
timeout: "1h", // Allow cancellation for up to 1 hour
},
],
},
{ event: "deployment/requested" },
async ({ event, step }) => {
const jobData = event.data as DeployJob;
return await step.run("execute-deployment", async () => {
logger.info("Deploying started");
try {
const result = await deploy(jobData);
logger.info("Deployment finished", result);
// Send success event
await inngest.send({
name: "deployment/completed",
data: {
...jobData,
result,
status: "success",
},
});
return result;
} catch (error) {
logger.error("Deployment failed", { jobData, error });
// Send failure event
await inngest.send({
name: "deployment/failed",
data: {
...jobData,
error: error instanceof Error ? error.message : String(error),
status: "failed",
},
});
throw error;
}
});
},
);
app.use(async (c, next) => {
if (c.req.path === "/health") {
if (c.req.path === "/health" || c.req.path === "/api/inngest") {
return next();
}
const authHeader = c.req.header("X-API-Key");
if (process.env.API_KEY !== authHeader) {
@@ -26,36 +95,97 @@ app.use(async (c, next) => {
return next();
});
app.post("/deploy", zValidator("json", deployJobSchema), (c) => {
app.post("/deploy", zValidator("json", deployJobSchema), async (c) => {
const data = c.req.valid("json");
queue.add(data, { groupName: data.serverId });
return c.json(
{
message: "Deployment Added",
},
200,
);
logger.info("Received deployment request", data);
try {
// Send event to Inngest instead of adding to Redis queue
await inngest.send({
name: "deployment/requested",
data,
});
logger.info("Deployment event sent to Inngest", {
serverId: data.serverId,
});
return c.json(
{
message: "Deployment Added to Inngest Queue",
serverId: data.serverId,
},
200,
);
} catch (error) {
console.log("error", error);
logger.error("Failed to send deployment event", error);
return c.json(
{
message: "Failed to queue deployment",
error: error instanceof Error ? error.message : String(error),
},
500,
);
}
});
app.post(
"/cancel-deployment",
zValidator("json", cancelDeploymentSchema),
async (c) => {
const data = c.req.valid("json");
logger.info("Received cancel deployment request", data);
try {
// Send cancellation event to Inngest
await inngest.send({
name: "deployment/cancelled",
data,
});
const identifier =
data.applicationType === "application"
? `applicationId: ${data.applicationId}`
: `composeId: ${data.composeId}`;
logger.info("Deployment cancellation event sent", {
...data,
identifier,
});
return c.json({
message: "Deployment cancellation requested",
applicationType: data.applicationType,
});
} catch (error) {
logger.error("Failed to send deployment cancellation event", error);
return c.json(
{
message: "Failed to cancel deployment",
error: error instanceof Error ? error.message : String(error),
},
500,
);
}
},
);
app.get("/health", async (c) => {
return c.json({ status: "ok" });
});
const queue = new Queue({
name: "deployments",
process: async (job: DeployJob) => {
logger.info("Deploying job", job);
return await deploy(job);
},
redisClient,
});
(async () => {
await redisClient.connect();
await redisClient.flushAll();
logger.info("Redis Cleaned");
})();
// Serve Inngest functions endpoint
app.on(
["GET", "POST", "PUT"],
"/api/inngest",
serveInngest({
client: inngest,
functions: [deploymentFunction],
}),
);
const port = Number.parseInt(process.env.PORT || "3000");
logger.info("Starting Deployments Server ✅", port);
logger.info("Starting Deployments Server with Inngest ✅", port);
serve({ fetch: app.fetch, port });
+19 -6
View File
@@ -3,8 +3,8 @@ import { z } from "zod";
export const deployJobSchema = z.discriminatedUnion("applicationType", [
z.object({
applicationId: z.string(),
titleLog: z.string(),
descriptionLog: z.string(),
titleLog: z.string().optional(),
descriptionLog: z.string().optional(),
server: z.boolean().optional(),
type: z.enum(["deploy", "redeploy"]),
applicationType: z.literal("application"),
@@ -12,8 +12,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
}),
z.object({
composeId: z.string(),
titleLog: z.string(),
descriptionLog: z.string(),
titleLog: z.string().optional(),
descriptionLog: z.string().optional(),
server: z.boolean().optional(),
type: z.enum(["deploy", "redeploy"]),
applicationType: z.literal("compose"),
@@ -22,8 +22,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
z.object({
applicationId: z.string(),
previewDeploymentId: z.string(),
titleLog: z.string(),
descriptionLog: z.string(),
titleLog: z.string().optional(),
descriptionLog: z.string().optional(),
server: z.boolean().optional(),
type: z.enum(["deploy"]),
applicationType: z.literal("application-preview"),
@@ -32,3 +32,16 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
]);
export type DeployJob = z.infer<typeof deployJobSchema>;
export const cancelDeploymentSchema = z.discriminatedUnion("applicationType", [
z.object({
applicationId: z.string(),
applicationType: z.literal("application"),
}),
z.object({
composeId: z.string(),
applicationType: z.literal("compose"),
}),
]);
export type CancelDeploymentJob = z.infer<typeof cancelDeploymentSchema>;
+13 -11
View File
@@ -18,14 +18,14 @@ export const deploy = async (job: DeployJob) => {
if (job.type === "redeploy") {
await rebuildRemoteApplication({
applicationId: job.applicationId,
titleLog: job.titleLog,
descriptionLog: job.descriptionLog,
titleLog: job.titleLog || "Rebuild deployment",
descriptionLog: job.descriptionLog || "",
});
} else if (job.type === "deploy") {
await deployRemoteApplication({
applicationId: job.applicationId,
titleLog: job.titleLog,
descriptionLog: job.descriptionLog,
titleLog: job.titleLog || "Manual deployment",
descriptionLog: job.descriptionLog || "",
});
}
}
@@ -38,14 +38,14 @@ export const deploy = async (job: DeployJob) => {
if (job.type === "redeploy") {
await rebuildRemoteCompose({
composeId: job.composeId,
titleLog: job.titleLog,
descriptionLog: job.descriptionLog,
titleLog: job.titleLog || "Rebuild deployment",
descriptionLog: job.descriptionLog || "",
});
} else if (job.type === "deploy") {
await deployRemoteCompose({
composeId: job.composeId,
titleLog: job.titleLog,
descriptionLog: job.descriptionLog,
titleLog: job.titleLog || "Manual deployment",
descriptionLog: job.descriptionLog || "",
});
}
}
@@ -57,14 +57,14 @@ export const deploy = async (job: DeployJob) => {
if (job.type === "deploy") {
await deployRemotePreviewApplication({
applicationId: job.applicationId,
titleLog: job.titleLog,
descriptionLog: job.descriptionLog,
titleLog: job.titleLog || "Preview Deployment",
descriptionLog: job.descriptionLog || "",
previewDeploymentId: job.previewDeploymentId,
});
}
}
}
} catch (_) {
} catch (e) {
if (job.applicationType === "application") {
await updateApplicationStatus(job.applicationId, "error");
} else if (job.applicationType === "compose") {
@@ -76,6 +76,8 @@ export const deploy = async (job: DeployJob) => {
previewStatus: "error",
});
}
throw e;
}
return true;
+10 -10
View File
@@ -1,7 +1,7 @@
import { addSuffixToAllProperties } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToAllProperties } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFile1 = `
version: "3.8"
@@ -61,7 +61,7 @@ secrets:
file: ./db_password.txt
`;
const expectedComposeFile1 = load(`
const expectedComposeFile1 = parse(`
version: "3.8"
services:
@@ -120,7 +120,7 @@ secrets:
`) as ComposeSpecification;
test("Add suffix to all properties in compose file 1", () => {
const composeData = load(composeFile1) as ComposeSpecification;
const composeData = parse(composeFile1) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
@@ -185,7 +185,7 @@ secrets:
file: ./db_password.txt
`;
const expectedComposeFile2 = load(`
const expectedComposeFile2 = parse(`
version: "3.8"
services:
@@ -243,7 +243,7 @@ secrets:
`) as ComposeSpecification;
test("Add suffix to all properties in compose file 2", () => {
const composeData = load(composeFile2) as ComposeSpecification;
const composeData = parse(composeFile2) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
@@ -308,7 +308,7 @@ secrets:
file: ./service_secret.txt
`;
const expectedComposeFile3 = load(`
const expectedComposeFile3 = parse(`
version: "3.8"
services:
@@ -366,7 +366,7 @@ secrets:
`) as ComposeSpecification;
test("Add suffix to all properties in compose file 3", () => {
const composeData = load(composeFile3) as ComposeSpecification;
const composeData = parse(composeFile3) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
@@ -420,7 +420,7 @@ volumes:
driver: local
`;
const expectedComposeFile = load(`
const expectedComposeFile = parse(`
version: "3.8"
services:
@@ -467,7 +467,7 @@ volumes:
`) as ComposeSpecification;
test("Add suffix to all properties in Plausible compose file", () => {
const composeData = load(composeFile) as ComposeSpecification;
const composeData = parse(composeFile) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToConfigsRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToConfigsRoot, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
test("Generate random hash with 8 characters", () => {
const hash = generateRandomHash();
@@ -24,7 +23,7 @@ configs:
`;
test("Add suffix to configs in root property", () => {
const composeData = load(composeFile) as ComposeSpecification;
const composeData = parse(composeFile) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -60,7 +59,7 @@ configs:
`;
test("Add suffix to multiple configs in root property", () => {
const composeData = load(composeFileMultipleConfigs) as ComposeSpecification;
const composeData = parse(composeFileMultipleConfigs) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -93,7 +92,7 @@ configs:
`;
test("Add suffix to configs with different properties in root property", () => {
const composeData = load(
const composeData = parse(
composeFileDifferentProperties,
) as ComposeSpecification;
@@ -138,7 +137,7 @@ configs:
`;
// Expected compose file con el prefijo `testhash`
const expectedComposeFileConfigRoot = load(`
const expectedComposeFileConfigRoot = parse(`
version: "3.8"
services:
@@ -163,7 +162,7 @@ configs:
`) as ComposeSpecification;
test("Add suffix to configs in root property", () => {
const composeData = load(composeFileConfigRoot) as ComposeSpecification;
const composeData = parse(composeFileConfigRoot) as ComposeSpecification;
const suffix = "testhash";
@@ -1,8 +1,10 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToConfigsInServices } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import {
addSuffixToConfigsInServices,
generateRandomHash,
} from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFile = `
version: "3.8"
@@ -20,7 +22,7 @@ configs:
`;
test("Add suffix to configs in services", () => {
const composeData = load(composeFile) as ComposeSpecification;
const composeData = parse(composeFile) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -52,7 +54,7 @@ configs:
`;
test("Add suffix to configs in services with single config", () => {
const composeData = load(
const composeData = parse(
composeFileSingleServiceConfig,
) as ComposeSpecification;
@@ -106,7 +108,7 @@ configs:
`;
test("Add suffix to configs in services with multiple configs", () => {
const composeData = load(
const composeData = parse(
composeFileMultipleServicesConfigs,
) as ComposeSpecification;
@@ -155,7 +157,7 @@ services:
`;
// Expected compose file con el prefijo `testhash`
const expectedComposeFileConfigServices = load(`
const expectedComposeFileConfigServices = parse(`
version: "3.8"
services:
@@ -180,7 +182,7 @@ services:
`) as ComposeSpecification;
test("Add suffix to configs in services", () => {
const composeData = load(composeFileConfigServices) as ComposeSpecification;
const composeData = parse(composeFileConfigServices) as ComposeSpecification;
const suffix = "testhash";
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToAllConfigs } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToAllConfigs, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
test("Generate random hash with 8 characters", () => {
const hash = generateRandomHash();
@@ -44,7 +43,7 @@ configs:
file: ./db-config.yml
`;
const expectedComposeFileCombinedConfigs = load(`
const expectedComposeFileCombinedConfigs = parse(`
version: "3.8"
services:
@@ -78,7 +77,7 @@ configs:
`) as ComposeSpecification;
test("Add suffix to all configs in root and services", () => {
const composeData = load(composeFileCombinedConfigs) as ComposeSpecification;
const composeData = parse(composeFileCombinedConfigs) as ComposeSpecification;
const suffix = "testhash";
@@ -123,7 +122,7 @@ configs:
file: ./db-config.yml
`;
const expectedComposeFileWithEnvAndExternal = load(`
const expectedComposeFileWithEnvAndExternal = parse(`
version: "3.8"
services:
@@ -160,7 +159,7 @@ configs:
`) as ComposeSpecification;
test("Add suffix to configs with environment and external", () => {
const composeData = load(
const composeData = parse(
composeFileWithEnvAndExternal,
) as ComposeSpecification;
@@ -201,7 +200,7 @@ configs:
file: ./db-config.yml
`;
const expectedComposeFileWithTemplateDriverAndLabels = load(`
const expectedComposeFileWithTemplateDriverAndLabels = parse(`
version: "3.8"
services:
@@ -232,7 +231,7 @@ configs:
`) as ComposeSpecification;
test("Add suffix to configs with template driver and labels", () => {
const composeData = load(
const composeData = parse(
composeFileWithTemplateDriverAndLabels,
) as ComposeSpecification;
@@ -108,4 +108,136 @@ describe("createDomainLabels", () => {
"traefik.http.services.test-app-1-web.loadbalancer.server.port=3000",
);
});
it("should add stripPath middleware when stripPath is enabled", async () => {
const stripPathDomain = {
...baseDomain,
path: "/api",
stripPath: true,
};
const labels = await createDomainLabels(appName, stripPathDomain, "web");
expect(labels).toContain(
"traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api",
);
expect(labels).toContain(
"traefik.http.routers.test-app-1-web.middlewares=stripprefix-test-app-1",
);
});
it("should add internalPath middleware when internalPath is set", async () => {
const internalPathDomain = {
...baseDomain,
internalPath: "/hello",
};
const webLabels = await createDomainLabels(
appName,
internalPathDomain,
"web",
);
const websecureLabels = await createDomainLabels(
appName,
internalPathDomain,
"websecure",
);
// Middleware definition should only appear in web entrypoint
expect(webLabels).toContain(
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
);
expect(websecureLabels).not.toContain(
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
);
// Both routers should reference the middleware
expect(webLabels).toContain(
"traefik.http.routers.test-app-1-web.middlewares=addprefix-test-app-1",
);
expect(websecureLabels).toContain(
"traefik.http.routers.test-app-1-websecure.middlewares=addprefix-test-app-1",
);
});
it("should combine HTTPS redirect with internalPath middleware in correct order", async () => {
const combinedDomain = {
...baseDomain,
https: true,
internalPath: "/hello",
};
const webLabels = await createDomainLabels(appName, combinedDomain, "web");
const websecureLabels = await createDomainLabels(
appName,
combinedDomain,
"websecure",
);
// Web entrypoint should have both middlewares with redirect first
expect(webLabels).toContain(
"traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file,addprefix-test-app-1",
);
// Websecure should only have the addprefix middleware
expect(websecureLabels).toContain(
"traefik.http.routers.test-app-1-websecure.middlewares=addprefix-test-app-1",
);
// Middleware definition should only appear once (in web)
expect(webLabels).toContain(
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
);
expect(websecureLabels).not.toContain(
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
);
});
it("should combine all middlewares in correct order", async () => {
const fullDomain = {
...baseDomain,
https: true,
path: "/api",
stripPath: true,
internalPath: "/hello",
};
const webLabels = await createDomainLabels(appName, fullDomain, "web");
// Should have all middleware definitions (only in web)
expect(webLabels).toContain(
"traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api",
);
expect(webLabels).toContain(
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
);
// Should have middlewares in correct order: redirect, stripprefix, addprefix
expect(webLabels).toContain(
"traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file,stripprefix-test-app-1,addprefix-test-app-1",
);
});
it("should not add middleware definitions for websecure entrypoint", async () => {
const internalPathDomain = {
...baseDomain,
path: "/api",
stripPath: true,
internalPath: "/hello",
};
const websecureLabels = await createDomainLabels(
appName,
internalPathDomain,
"websecure",
);
// Should not contain any middleware definitions
expect(websecureLabels).not.toContain(
"traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api",
);
expect(websecureLabels).not.toContain(
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
);
// But should reference the middlewares
expect(websecureLabels).toContain(
"traefik.http.routers.test-app-1-websecure.middlewares=stripprefix-test-app-1,addprefix-test-app-1",
);
});
});
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToNetworksRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToNetworksRoot, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFile = `
version: "3.8"
@@ -36,7 +35,7 @@ test("Generate random hash with 8 characters", () => {
});
test("Add suffix to networks root property", () => {
const composeData = load(composeFile) as ComposeSpecification;
const composeData = parse(composeFile) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -80,7 +79,7 @@ networks:
`;
test("Add suffix to advanced networks root property (2 TRY)", () => {
const composeData = load(composeFile2) as ComposeSpecification;
const composeData = parse(composeFile2) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -121,7 +120,7 @@ networks:
`;
test("Add suffix to networks with external properties", () => {
const composeData = load(composeFile3) as ComposeSpecification;
const composeData = parse(composeFile3) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -161,7 +160,7 @@ networks:
`;
test("Add suffix to networks with IPAM configurations", () => {
const composeData = load(composeFile4) as ComposeSpecification;
const composeData = parse(composeFile4) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -202,7 +201,7 @@ networks:
`;
test("Add suffix to networks with custom options", () => {
const composeData = load(composeFile5) as ComposeSpecification;
const composeData = parse(composeFile5) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -265,7 +264,7 @@ networks:
`;
test("Add suffix to networks with static suffix", () => {
const composeData = load(composeFile6) as ComposeSpecification;
const composeData = parse(composeFile6) as ComposeSpecification;
const suffix = "testhash";
@@ -274,7 +273,7 @@ test("Add suffix to networks with static suffix", () => {
}
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
const expectedComposeData = load(
const expectedComposeData = parse(
expectedComposeFile6,
) as ComposeSpecification;
expect(networks).toStrictEqual(expectedComposeData.networks);
@@ -294,7 +293,7 @@ networks:
`;
test("It shoudn't add suffix to dokploy-network", () => {
const composeData = load(composeFile7) as ComposeSpecification;
const composeData = parse(composeFile7) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -1,8 +1,10 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNetworks } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import {
addSuffixToServiceNetworks,
generateRandomHash,
} from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFile = `
version: "3.8"
@@ -21,7 +23,7 @@ services:
`;
test("Add suffix to networks in services", () => {
const composeData = load(composeFile) as ComposeSpecification;
const composeData = parse(composeFile) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -65,7 +67,7 @@ networks:
`;
test("Add suffix to networks in services with aliases", () => {
const composeData = load(composeFile2) as ComposeSpecification;
const composeData = parse(composeFile2) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -105,7 +107,7 @@ networks:
`;
test("Add suffix to networks in services (Object with simple networks)", () => {
const composeData = load(composeFile3) as ComposeSpecification;
const composeData = parse(composeFile3) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -151,7 +153,7 @@ networks:
`;
test("Add suffix to networks in services (combined case)", () => {
const composeData = load(composeFileCombined) as ComposeSpecification;
const composeData = parse(composeFileCombined) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -194,7 +196,7 @@ services:
`;
test("It shoudn't add suffix to dokploy-network in services", () => {
const composeData = load(composeFile7) as ComposeSpecification;
const composeData = parse(composeFile7) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -243,7 +245,7 @@ services:
`;
test("It shoudn't add suffix to dokploy-network in services multiples cases", () => {
const composeData = load(composeFile8) as ComposeSpecification;
const composeData = parse(composeFile8) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -1,12 +1,12 @@
import { generateRandomHash } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import {
addSuffixToAllNetworks,
addSuffixToNetworksRoot,
addSuffixToServiceNetworks,
generateRandomHash,
} from "@dokploy/server";
import { addSuffixToNetworksRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFileCombined = `
version: "3.8"
@@ -39,7 +39,7 @@ networks:
`;
test("Add suffix to networks in services and root (combined case)", () => {
const composeData = load(composeFileCombined) as ComposeSpecification;
const composeData = parse(composeFileCombined) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -89,7 +89,7 @@ test("Add suffix to networks in services and root (combined case)", () => {
expect(redisNetworks).not.toHaveProperty("backend");
});
const expectedComposeFile = load(`
const expectedComposeFile = parse(`
version: "3.8"
services:
@@ -120,7 +120,7 @@ networks:
`);
test("Add suffix to networks in compose file", () => {
const composeData = load(composeFileCombined) as ComposeSpecification;
const composeData = parse(composeFileCombined) as ComposeSpecification;
const suffix = "testhash";
if (!composeData?.networks) {
@@ -156,7 +156,7 @@ networks:
driver: bridge
`;
const expectedComposeFile2 = load(`
const expectedComposeFile2 = parse(`
version: "3.8"
services:
@@ -182,7 +182,7 @@ networks:
`);
test("Add suffix to networks in compose file with external and internal networks", () => {
const composeData = load(composeFile2) as ComposeSpecification;
const composeData = parse(composeFile2) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
@@ -218,7 +218,7 @@ networks:
com.docker.network.bridge.enable_icc: "true"
`;
const expectedComposeFile3 = load(`
const expectedComposeFile3 = parse(`
version: "3.8"
services:
@@ -247,7 +247,7 @@ networks:
`);
test("Add suffix to networks in compose file with multiple services and complex network configurations", () => {
const composeData = load(composeFile3) as ComposeSpecification;
const composeData = parse(composeFile3) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
@@ -289,7 +289,7 @@ networks:
`;
const expectedComposeFile4 = load(`
const expectedComposeFile4 = parse(`
version: "3.8"
services:
@@ -326,7 +326,7 @@ networks:
`);
test("Expect don't add suffix to dokploy-network in compose file with multiple services and complex network configurations", () => {
const composeData = load(composeFile4) as ComposeSpecification;
const composeData = parse(composeFile4) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToSecretsRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToSecretsRoot, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
test("Generate random hash with 8 characters", () => {
const hash = generateRandomHash();
@@ -24,7 +23,7 @@ secrets:
`;
test("Add suffix to secrets in root property", () => {
const composeData = load(composeFileSecretsRoot) as ComposeSpecification;
const composeData = parse(composeFileSecretsRoot) as ComposeSpecification;
const suffix = generateRandomHash();
if (!composeData?.secrets) {
@@ -53,7 +52,7 @@ secrets:
`;
test("Add suffix to secrets in root property (Test 1)", () => {
const composeData = load(composeFileSecretsRoot1) as ComposeSpecification;
const composeData = parse(composeFileSecretsRoot1) as ComposeSpecification;
const suffix = generateRandomHash();
if (!composeData?.secrets) {
@@ -85,7 +84,7 @@ secrets:
`;
test("Add suffix to secrets in root property (Test 2)", () => {
const composeData = load(composeFileSecretsRoot2) as ComposeSpecification;
const composeData = parse(composeFileSecretsRoot2) as ComposeSpecification;
const suffix = generateRandomHash();
if (!composeData?.secrets) {
@@ -1,8 +1,10 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToSecretsInServices } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import {
addSuffixToSecretsInServices,
generateRandomHash,
} from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFileSecretsServices = `
version: "3.8"
@@ -19,7 +21,7 @@ secrets:
`;
test("Add suffix to secrets in services", () => {
const composeData = load(composeFileSecretsServices) as ComposeSpecification;
const composeData = parse(composeFileSecretsServices) as ComposeSpecification;
const suffix = generateRandomHash();
if (!composeData.services) {
@@ -52,7 +54,9 @@ secrets:
`;
test("Add suffix to secrets in services (Test 1)", () => {
const composeData = load(composeFileSecretsServices1) as ComposeSpecification;
const composeData = parse(
composeFileSecretsServices1,
) as ComposeSpecification;
const suffix = generateRandomHash();
if (!composeData.services) {
@@ -91,7 +95,9 @@ secrets:
`;
test("Add suffix to secrets in services (Test 2)", () => {
const composeData = load(composeFileSecretsServices2) as ComposeSpecification;
const composeData = parse(
composeFileSecretsServices2,
) as ComposeSpecification;
const suffix = generateRandomHash();
if (!composeData.services) {
@@ -1,7 +1,7 @@
import { addSuffixToAllSecrets } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToAllSecrets } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFileCombinedSecrets = `
version: "3.8"
@@ -25,7 +25,7 @@ secrets:
file: ./app_secret.txt
`;
const expectedComposeFileCombinedSecrets = load(`
const expectedComposeFileCombinedSecrets = parse(`
version: "3.8"
services:
@@ -48,7 +48,7 @@ secrets:
`) as ComposeSpecification;
test("Add suffix to all secrets", () => {
const composeData = load(composeFileCombinedSecrets) as ComposeSpecification;
const composeData = parse(composeFileCombinedSecrets) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
@@ -77,7 +77,7 @@ secrets:
file: ./cache_secret.txt
`;
const expectedComposeFileCombinedSecrets3 = load(`
const expectedComposeFileCombinedSecrets3 = parse(`
version: "3.8"
services:
@@ -99,7 +99,9 @@ secrets:
`) as ComposeSpecification;
test("Add suffix to all secrets (3rd Case)", () => {
const composeData = load(composeFileCombinedSecrets3) as ComposeSpecification;
const composeData = parse(
composeFileCombinedSecrets3,
) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
@@ -128,7 +130,7 @@ secrets:
file: ./db_password.txt
`;
const expectedComposeFileCombinedSecrets4 = load(`
const expectedComposeFileCombinedSecrets4 = parse(`
version: "3.8"
services:
@@ -150,7 +152,9 @@ secrets:
`) as ComposeSpecification;
test("Add suffix to all secrets (4th Case)", () => {
const composeData = load(composeFileCombinedSecrets4) as ComposeSpecification;
const composeData = parse(
composeFileCombinedSecrets4,
) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFile = `
version: "3.8"
@@ -28,7 +27,7 @@ test("Generate random hash with 8 characters", () => {
});
test("Add suffix to service names with container_name in compose file", () => {
const composeData = load(composeFile) as ComposeSpecification;
const composeData = parse(composeFile) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
test("Generate random hash with 8 characters", () => {
const hash = generateRandomHash();
@@ -33,7 +32,7 @@ networks:
`;
test("Add suffix to service names with depends_on (array) in compose file", () => {
const composeData = load(composeFile4) as ComposeSpecification;
const composeData = parse(composeFile4) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -103,7 +102,7 @@ networks:
`;
test("Add suffix to service names with depends_on (object) in compose file", () => {
const composeData = load(composeFile5) as ComposeSpecification;
const composeData = parse(composeFile5) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
test("Generate random hash with 8 characters", () => {
const hash = generateRandomHash();
@@ -31,7 +30,7 @@ networks:
`;
test("Add suffix to service names with extends (string) in compose file", () => {
const composeData = load(composeFile6) as ComposeSpecification;
const composeData = parse(composeFile6) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -91,7 +90,7 @@ networks:
`;
test("Add suffix to service names with extends (object) in compose file", () => {
const composeData = load(composeFile7) as ComposeSpecification;
const composeData = parse(composeFile7) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
test("Generate random hash with 8 characters", () => {
const hash = generateRandomHash();
@@ -32,7 +31,7 @@ networks:
`;
test("Add suffix to service names with links in compose file", () => {
const composeData = load(composeFile2) as ComposeSpecification;
const composeData = parse(composeFile2) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
test("Generate random hash with 8 characters", () => {
const hash = generateRandomHash();
@@ -27,7 +26,7 @@ networks:
`;
test("Add suffix to service names in compose file", () => {
const composeData = load(composeFile) as ComposeSpecification;
const composeData = parse(composeFile) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -1,10 +1,10 @@
import type { ComposeSpecification } from "@dokploy/server";
import {
addSuffixToAllServiceNames,
addSuffixToServiceNames,
} from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFileCombinedAllCases = `
version: "3.8"
@@ -38,7 +38,7 @@ networks:
driver: bridge
`;
const expectedComposeFile = load(`
const expectedComposeFile = parse(`
version: "3.8"
services:
@@ -71,7 +71,9 @@ networks:
`);
test("Add suffix to all service names in compose file", () => {
const composeData = load(composeFileCombinedAllCases) as ComposeSpecification;
const composeData = parse(
composeFileCombinedAllCases,
) as ComposeSpecification;
const suffix = "testhash";
@@ -131,7 +133,7 @@ networks:
driver: bridge
`;
const expectedComposeFile1 = load(`
const expectedComposeFile1 = parse(`
version: "3.8"
services:
@@ -176,7 +178,7 @@ networks:
`) as ComposeSpecification;
test("Add suffix to all service names in compose file 1", () => {
const composeData = load(composeFile1) as ComposeSpecification;
const composeData = parse(composeFile1) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
@@ -227,7 +229,7 @@ networks:
driver: bridge
`;
const expectedComposeFile2 = load(`
const expectedComposeFile2 = parse(`
version: "3.8"
services:
@@ -271,7 +273,7 @@ networks:
`) as ComposeSpecification;
test("Add suffix to all service names in compose file 2", () => {
const composeData = load(composeFile2) as ComposeSpecification;
const composeData = parse(composeFile2) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
@@ -322,7 +324,7 @@ networks:
driver: bridge
`;
const expectedComposeFile3 = load(`
const expectedComposeFile3 = parse(`
version: "3.8"
services:
@@ -366,7 +368,7 @@ networks:
`) as ComposeSpecification;
test("Add suffix to all service names in compose file 3", () => {
const composeData = load(composeFile3) as ComposeSpecification;
const composeData = parse(composeFile3) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
test("Generate random hash with 8 characters", () => {
const hash = generateRandomHash();
@@ -36,7 +35,7 @@ networks:
`;
test("Add suffix to service names with volumes_from in compose file", () => {
const composeData = load(composeFile3) as ComposeSpecification;
const composeData = parse(composeFile3) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -1,8 +1,11 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToAllVolumes, addSuffixToVolumesRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import {
addSuffixToAllVolumes,
addSuffixToVolumesRoot,
generateRandomHash,
} from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFile = `
services:
@@ -67,7 +70,7 @@ volumes:
driver: local
`;
const expectedDockerCompose = load(`
const expectedDockerCompose = parse(`
services:
mail:
image: bytemark/smtp
@@ -140,7 +143,7 @@ test("Generate random hash with 8 characters", () => {
// Docker compose needs unique names for services, volumes, networks and containers
// So base on a input which is a dockercompose file, it should replace the name with a hash and return a new dockercompose file
test("Add suffix to volumes root property", () => {
const composeData = load(composeFile) as ComposeSpecification;
const composeData = parse(composeFile) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -162,7 +165,7 @@ test("Add suffix to volumes root property", () => {
});
test("Expect to change the suffix in all the possible places", () => {
const composeData = load(composeFile) as ComposeSpecification;
const composeData = parse(composeFile) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
@@ -192,7 +195,7 @@ volumes:
mongo-data:
`;
const expectedDockerCompose2 = load(`
const expectedDockerCompose2 = parse(`
version: '3.8'
services:
app:
@@ -215,7 +218,7 @@ volumes:
`) as ComposeSpecification;
test("Expect to change the suffix in all the possible places (2 Try)", () => {
const composeData = load(composeFile2) as ComposeSpecification;
const composeData = parse(composeFile2) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
@@ -245,7 +248,7 @@ volumes:
mongo-data:
`;
const expectedDockerCompose3 = load(`
const expectedDockerCompose3 = parse(`
version: '3.8'
services:
app:
@@ -268,7 +271,7 @@ volumes:
`) as ComposeSpecification;
test("Expect to change the suffix in all the possible places (3 Try)", () => {
const composeData = load(composeFile3) as ComposeSpecification;
const composeData = parse(composeFile3) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
@@ -642,7 +645,7 @@ volumes:
db-config:
`;
const expectedDockerComposeComplex = load(`
const expectedDockerComposeComplex = parse(`
version: "3.8"
services:
studio:
@@ -1009,7 +1012,7 @@ volumes:
`);
test("Expect to change the suffix in all the possible places (4 Try)", () => {
const composeData = load(composeFileComplex) as ComposeSpecification;
const composeData = parse(composeFileComplex) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
@@ -1062,7 +1065,7 @@ volumes:
db-data:
`;
const expectedDockerComposeExample1 = load(`
const expectedDockerComposeExample1 = parse(`
version: "3.8"
services:
web:
@@ -1108,7 +1111,7 @@ volumes:
`) as ComposeSpecification;
test("Expect to change the suffix in all the possible places (5 Try)", () => {
const composeData = load(composeFileExample1) as ComposeSpecification;
const composeData = parse(composeFileExample1) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
@@ -1140,7 +1143,7 @@ volumes:
backrest-cache:
`;
const expectedDockerComposeBackrest = load(`
const expectedDockerComposeBackrest = parse(`
services:
backrest:
image: garethgeorge/backrest:v1.7.3
@@ -1165,7 +1168,7 @@ volumes:
`) as ComposeSpecification;
test("Should handle volume paths with subdirectories correctly", () => {
const composeData = load(composeFileBackrest) as ComposeSpecification;
const composeData = parse(composeFileBackrest) as ComposeSpecification;
const suffix = "testhash";
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
@@ -1,8 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToVolumesRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToVolumesRoot, generateRandomHash } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFile = `
version: "3.8"
@@ -30,7 +29,7 @@ test("Generate random hash with 8 characters", () => {
});
test("Add suffix to volumes in root property", () => {
const composeData = load(composeFile) as ComposeSpecification;
const composeData = parse(composeFile) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -68,7 +67,7 @@ networks:
`;
test("Add suffix to volumes in root property (Case 2)", () => {
const composeData = load(composeFile2) as ComposeSpecification;
const composeData = parse(composeFile2) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -102,7 +101,7 @@ networks:
`;
test("Add suffix to volumes in root property (Case 3)", () => {
const composeData = load(composeFile3) as ComposeSpecification;
const composeData = parse(composeFile3) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -149,7 +148,7 @@ volumes:
`;
// Expected compose file con el prefijo `testhash`
const expectedComposeFile4 = load(`
const expectedComposeFile4 = parse(`
version: "3.8"
services:
@@ -180,7 +179,7 @@ volumes:
`) as ComposeSpecification;
test("Add suffix to volumes in root property", () => {
const composeData = load(composeFile4) as ComposeSpecification;
const composeData = parse(composeFile4) as ComposeSpecification;
const suffix = "testhash";
@@ -1,8 +1,10 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToVolumesInServices } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import {
addSuffixToVolumesInServices,
generateRandomHash,
} from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
test("Generate random hash with 8 characters", () => {
const hash = generateRandomHash();
@@ -22,7 +24,7 @@ services:
`;
test("Add suffix to volumes declared directly in services", () => {
const composeData = load(composeFile1) as ComposeSpecification;
const composeData = parse(composeFile1) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -57,7 +59,7 @@ volumes:
`;
test("Add suffix to volumes declared directly in services (Case 2)", () => {
const composeData = load(composeFileTypeVolume) as ComposeSpecification;
const composeData = parse(composeFileTypeVolume) as ComposeSpecification;
const suffix = generateRandomHash();
@@ -1,7 +1,7 @@
import { addSuffixToAllVolumes } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { load } from "js-yaml";
import { addSuffixToAllVolumes } from "@dokploy/server";
import { expect, test } from "vitest";
import { parse } from "yaml";
const composeFileTypeVolume = `
version: "3.8"
@@ -23,7 +23,7 @@ volumes:
driver: local
`;
const expectedComposeFileTypeVolume = load(`
const expectedComposeFileTypeVolume = parse(`
version: "3.8"
services:
@@ -44,7 +44,7 @@ volumes:
`) as ComposeSpecification;
test("Add suffix to volumes with type: volume in services", () => {
const composeData = load(composeFileTypeVolume) as ComposeSpecification;
const composeData = parse(composeFileTypeVolume) as ComposeSpecification;
const suffix = "testhash";
@@ -73,7 +73,7 @@ volumes:
driver: local
`;
const expectedComposeFileTypeVolume1 = load(`
const expectedComposeFileTypeVolume1 = parse(`
version: "3.8"
services:
@@ -93,7 +93,7 @@ volumes:
`) as ComposeSpecification;
test("Add suffix to mixed volumes in services", () => {
const composeData = load(composeFileTypeVolume1) as ComposeSpecification;
const composeData = parse(composeFileTypeVolume1) as ComposeSpecification;
const suffix = "testhash";
@@ -128,7 +128,7 @@ volumes:
device: /path/to/app/logs
`;
const expectedComposeFileTypeVolume2 = load(`
const expectedComposeFileTypeVolume2 = parse(`
version: "3.8"
services:
@@ -154,7 +154,7 @@ volumes:
`) as ComposeSpecification;
test("Add suffix to complex volume configurations in services", () => {
const composeData = load(composeFileTypeVolume2) as ComposeSpecification;
const composeData = parse(composeFileTypeVolume2) as ComposeSpecification;
const suffix = "testhash";
@@ -218,7 +218,7 @@ volumes:
device: /path/to/shared/logs
`;
const expectedComposeFileTypeVolume3 = load(`
const expectedComposeFileTypeVolume3 = parse(`
version: "3.8"
services:
@@ -273,7 +273,7 @@ volumes:
`) as ComposeSpecification;
test("Add suffix to complex nested volumes configuration in services", () => {
const composeData = load(composeFileTypeVolume3) as ComposeSpecification;
const composeData = parse(composeFileTypeVolume3) as ComposeSpecification;
const suffix = "testhash";
+1 -1
View File
@@ -1,5 +1,5 @@
import { extractCommitMessage } from "@/pages/api/deploy/[refreshToken]";
import { describe, expect, it } from "vitest";
import { extractCommitMessage } from "@/pages/api/deploy/[refreshToken]";
describe("GitHub Webhook Skip CI", () => {
const mockGithubHeaders = {
@@ -1,12 +1,12 @@
import fs from "node:fs/promises";
import path from "node:path";
import { paths } from "@dokploy/server/constants";
const { APPLICATIONS_PATH } = paths();
import type { ApplicationNested } from "@dokploy/server";
import { unzipDrop } from "@dokploy/server";
import { paths } from "@dokploy/server/constants";
import AdmZip from "adm-zip";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
const { APPLICATIONS_PATH } = paths();
vi.mock("@dokploy/server/constants", async (importOriginal) => {
const actual = await importOriginal();
return {
@@ -25,10 +25,13 @@ if (typeof window === "undefined") {
}
const baseApp: ApplicationNested = {
railpackVersion: "0.2.2",
applicationId: "",
previewLabels: [],
herokuVersion: "",
giteaBranch: "",
giteaBuildPath: "",
previewRequireCollaboratorPermissions: false,
giteaId: "",
giteaOwner: "",
giteaRepository: "",
@@ -53,13 +56,21 @@ const baseApp: ApplicationNested = {
previewPort: 3000,
previewLimit: 0,
previewWildcard: "",
project: {
environment: {
env: "",
organizationId: "",
environmentId: "",
name: "",
description: "",
createdAt: "",
description: "",
projectId: "",
project: {
env: "",
organizationId: "",
name: "",
description: "",
createdAt: "",
projectId: "",
},
},
buildArgs: null,
buildPath: "/",
@@ -89,6 +100,7 @@ const baseApp: ApplicationNested = {
dockerfile: null,
dockerImage: null,
dropBuildPath: null,
environmentId: "",
enabled: null,
env: null,
healthCheckSwarm: null,
@@ -103,7 +115,6 @@ const baseApp: ApplicationNested = {
password: null,
placementSwarm: null,
ports: [],
projectId: "",
publishDirectory: null,
isStaticSpa: null,
redirects: [],
@@ -122,6 +133,7 @@ const baseApp: ApplicationNested = {
username: null,
dockerContextPath: null,
rollbackActive: false,
stopGracePeriodSwarm: null,
};
describe("unzipDrop using real zip files", () => {
@@ -141,7 +153,7 @@ describe("unzipDrop using real zip files", () => {
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
const zip = new AdmZip("./__test__/drop/zips/single-file.zip");
console.log(`Output Path: ${outputPath}`);
const zipBuffer = zip.toBuffer();
const zipBuffer = zip.toBuffer() as Buffer<ArrayBuffer>;
const file = new File([zipBuffer], "single.zip");
await unzipDrop(file, baseApp);
const files = await fs.readdir(outputPath, { withFileTypes: true });
+335
View File
@@ -0,0 +1,335 @@
import { prepareEnvironmentVariables } from "@dokploy/server/index";
import { describe, expect, it } from "vitest";
const projectEnv = `
ENVIRONMENT=staging
DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db
PORT=3000
`;
const environmentEnv = `
NODE_ENV=development
API_URL=https://api.dev.example.com
REDIS_URL=redis://localhost:6379
DATABASE_NAME=dev_database
SECRET_KEY=env-secret-123
`;
describe("prepareEnvironmentVariables (environment variables)", () => {
it("resolves environment variables correctly", () => {
const serviceWithEnvVars = `
NODE_ENV=\${{environment.NODE_ENV}}
API_URL=\${{environment.API_URL}}
SERVICE_PORT=4000
`;
const resolved = prepareEnvironmentVariables(
serviceWithEnvVars,
"",
environmentEnv,
);
expect(resolved).toEqual([
"NODE_ENV=development",
"API_URL=https://api.dev.example.com",
"SERVICE_PORT=4000",
]);
});
it("resolves both project and environment variables", () => {
const serviceWithBoth = `
ENVIRONMENT=\${{project.ENVIRONMENT}}
NODE_ENV=\${{environment.NODE_ENV}}
API_URL=\${{environment.API_URL}}
DATABASE_URL=\${{project.DATABASE_URL}}
SERVICE_PORT=4000
`;
const resolved = prepareEnvironmentVariables(
serviceWithBoth,
projectEnv,
environmentEnv,
);
expect(resolved).toEqual([
"ENVIRONMENT=staging",
"NODE_ENV=development",
"API_URL=https://api.dev.example.com",
"DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db",
"SERVICE_PORT=4000",
]);
});
it("handles undefined environment variables", () => {
const serviceWithUndefined = `
UNDEFINED_VAR=\${{environment.UNDEFINED_VAR}}
`;
expect(() =>
prepareEnvironmentVariables(serviceWithUndefined, "", environmentEnv),
).toThrow("Invalid environment variable: environment.UNDEFINED_VAR");
});
it("allows service variables to override environment variables", () => {
const serviceOverrideEnv = `
NODE_ENV=production
API_URL=\${{environment.API_URL}}
`;
const resolved = prepareEnvironmentVariables(
serviceOverrideEnv,
"",
environmentEnv,
);
expect(resolved).toEqual([
"NODE_ENV=production", // Overrides environment variable
"API_URL=https://api.dev.example.com",
]);
});
it("resolves complex references with project, environment, and service variables", () => {
const complexServiceEnv = `
FULL_DATABASE_URL=\${{project.DATABASE_URL}}/\${{environment.DATABASE_NAME}}
API_ENDPOINT=\${{environment.API_URL}}/\${{project.ENVIRONMENT}}/api
SERVICE_NAME=my-service
COMPLEX_VAR=\${{SERVICE_NAME}}-\${{environment.NODE_ENV}}-\${{project.ENVIRONMENT}}
`;
const resolved = prepareEnvironmentVariables(
complexServiceEnv,
projectEnv,
environmentEnv,
);
expect(resolved).toEqual([
"FULL_DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db/dev_database",
"API_ENDPOINT=https://api.dev.example.com/staging/api",
"SERVICE_NAME=my-service",
"COMPLEX_VAR=my-service-development-staging",
]);
});
it("handles environment variables with special characters", () => {
const specialEnvVars = `
SPECIAL_URL=https://special.com
COMPLEX_KEY="key-with-@#$%^&*()"
JWT_SECRET="secret-with-spaces and symbols!@#"
`;
const serviceWithSpecial = `
FULL_URL=\${{environment.SPECIAL_URL}}/path?key=\${{environment.COMPLEX_KEY}}
AUTH_SECRET=\${{environment.JWT_SECRET}}
`;
const resolved = prepareEnvironmentVariables(
serviceWithSpecial,
"",
specialEnvVars,
);
expect(resolved).toEqual([
"FULL_URL=https://special.com/path?key=key-with-@#$%^&*()",
"AUTH_SECRET=secret-with-spaces and symbols!@#",
]);
});
it("maintains precedence: service > environment > project", () => {
const conflictingProjectEnv = `
NODE_ENV=production-project
API_URL=https://project.api.com
DATABASE_NAME=project_db
`;
const conflictingEnvironmentEnv = `
NODE_ENV=development-environment
API_URL=https://environment.api.com
DATABASE_NAME=env_db
`;
const serviceWithConflicts = `
NODE_ENV=service-override
PROJECT_ENV=\${{project.NODE_ENV}}
ENV_VAR=\${{environment.API_URL}}
DB_NAME=\${{environment.DATABASE_NAME}}
`;
const resolved = prepareEnvironmentVariables(
serviceWithConflicts,
conflictingProjectEnv,
conflictingEnvironmentEnv,
);
expect(resolved).toEqual([
"NODE_ENV=service-override", // Service wins
"PROJECT_ENV=production-project", // Project reference
"ENV_VAR=https://environment.api.com", // Environment reference
"DB_NAME=env_db", // Environment reference
]);
});
it("handles empty environment variables", () => {
const serviceWithEmpty = `
SERVICE_VAR=test
PROJECT_VAR=\${{project.ENVIRONMENT}}
`;
const resolved = prepareEnvironmentVariables(
serviceWithEmpty,
projectEnv,
"",
);
expect(resolved).toEqual(["SERVICE_VAR=test", "PROJECT_VAR=staging"]);
});
it("handles mixed quotes and environment variables", () => {
const envWithQuotes = `
QUOTED_VAR="development"
SINGLE_QUOTED='https://api.dev.example.com'
MIXED_VAR="value with 'single' quotes"
`;
const serviceWithQuotes = `
NODE_ENV=\${{environment.QUOTED_VAR}}
API_URL=\${{environment.SINGLE_QUOTED}}
COMPLEX="Prefix-\${{environment.MIXED_VAR}}-Suffix"
`;
const resolved = prepareEnvironmentVariables(
serviceWithQuotes,
"",
envWithQuotes,
);
expect(resolved).toEqual([
"NODE_ENV=development",
"API_URL=https://api.dev.example.com",
"COMPLEX=Prefix-value with 'single' quotes-Suffix",
]);
});
it("resolves multiple environment references in single value", () => {
const multiRefEnv = `
HOST=localhost
PORT=5432
USERNAME=postgres
PASSWORD=secret123
`;
const serviceWithMultiRefs = `
DATABASE_URL=postgresql://\${{environment.USERNAME}}:\${{environment.PASSWORD}}@\${{environment.HOST}}:\${{environment.PORT}}/mydb
CONNECTION_STRING=\${{environment.HOST}}:\${{environment.PORT}}
`;
const resolved = prepareEnvironmentVariables(
serviceWithMultiRefs,
"",
multiRefEnv,
);
expect(resolved).toEqual([
"DATABASE_URL=postgresql://postgres:secret123@localhost:5432/mydb",
"CONNECTION_STRING=localhost:5432",
]);
});
it("handles nested references with environment and project variables", () => {
const nestedProjectEnv = `
BASE_DOMAIN=example.com
PROTOCOL=https
`;
const nestedEnvironmentEnv = `
SUBDOMAIN=api.dev
PATH_PREFIX=/v1
`;
const serviceWithNested = `
FULL_URL=\${{project.PROTOCOL}}://\${{environment.SUBDOMAIN}}.\${{project.BASE_DOMAIN}}\${{environment.PATH_PREFIX}}/endpoint
API_BASE=\${{project.PROTOCOL}}://\${{environment.SUBDOMAIN}}.\${{project.BASE_DOMAIN}}
`;
const resolved = prepareEnvironmentVariables(
serviceWithNested,
nestedProjectEnv,
nestedEnvironmentEnv,
);
expect(resolved).toEqual([
"FULL_URL=https://api.dev.example.com/v1/endpoint",
"API_BASE=https://api.dev.example.com",
]);
});
it("throws error for malformed environment variable references", () => {
const serviceWithMalformed = `
MALFORMED1=\${{environment.}}
MALFORMED2=\${{environment}}
VALID=\${{environment.NODE_ENV}}
`;
// Should throw error for empty variable name after environment.
expect(() =>
prepareEnvironmentVariables(serviceWithMalformed, "", environmentEnv),
).toThrow("Invalid environment variable: environment.");
});
it("handles environment variables with numeric values", () => {
const numericEnv = `
PORT=8080
TIMEOUT=30
RETRY_COUNT=3
PERCENTAGE=99.5
`;
const serviceWithNumeric = `
SERVER_PORT=\${{environment.PORT}}
REQUEST_TIMEOUT=\${{environment.TIMEOUT}}
MAX_RETRIES=\${{environment.RETRY_COUNT}}
SUCCESS_RATE=\${{environment.PERCENTAGE}}
`;
const resolved = prepareEnvironmentVariables(
serviceWithNumeric,
"",
numericEnv,
);
expect(resolved).toEqual([
"SERVER_PORT=8080",
"REQUEST_TIMEOUT=30",
"MAX_RETRIES=3",
"SUCCESS_RATE=99.5",
]);
});
it("handles boolean-like environment variables", () => {
const booleanEnv = `
DEBUG=true
ENABLED=false
PRODUCTION=1
DEVELOPMENT=0
`;
const serviceWithBoolean = `
DEBUG_MODE=\${{environment.DEBUG}}
FEATURE_ENABLED=\${{environment.ENABLED}}
IS_PROD=\${{environment.PRODUCTION}}
IS_DEV=\${{environment.DEVELOPMENT}}
`;
const resolved = prepareEnvironmentVariables(
serviceWithBoolean,
"",
booleanEnv,
);
expect(resolved).toEqual([
"DEBUG_MODE=true",
"FEATURE_ENABLED=false",
"IS_PROD=1",
"IS_DEV=0",
]);
});
});
+74
View File
@@ -177,3 +177,77 @@ COMPLEX_VAR="'Prefix \"DoubleQuoted\" and \${{project.APP_NAME}}'"
]);
});
});
describe("prepareEnvironmentVariables (self references)", () => {
it("resolves self references correctly", () => {
const serviceEnv = `
ENVIRONMENT=staging
DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db
SELF_REF=\${{ENVIRONMENT}}
`;
const resolved = prepareEnvironmentVariables(serviceEnv, "");
expect(resolved).toEqual([
"ENVIRONMENT=staging",
"DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db",
"SELF_REF=staging",
]);
});
it("throws on undefined self references", () => {
const serviceEnv = `
MISSING_VAR=\${{UNDEFINED_VAR}}
`;
expect(() => prepareEnvironmentVariables(serviceEnv, "")).toThrow(
"Invalid service environment variable: UNDEFINED_VAR",
);
});
it("allows overriding and still resolving from self", () => {
const serviceEnv = `
ENVIRONMENT=production
OVERRIDE_ENV=\${{ENVIRONMENT}}
`;
const resolved = prepareEnvironmentVariables(serviceEnv, "");
expect(resolved).toEqual([
"ENVIRONMENT=production",
"OVERRIDE_ENV=production",
]);
});
it("resolves multiple self references inside one value", () => {
const serviceEnv = `
ENVIRONMENT=staging
APP_NAME=MyApp
COMPLEX=\${{APP_NAME}}-\${{ENVIRONMENT}}-\${{APP_NAME}}
`;
const resolved = prepareEnvironmentVariables(serviceEnv, "");
expect(resolved).toEqual([
"ENVIRONMENT=staging",
"APP_NAME=MyApp",
"COMPLEX=MyApp-staging-MyApp",
]);
});
it("handles quotes with self references", () => {
const serviceEnv = `
ENVIRONMENT=production
QUOTED="'\${{ENVIRONMENT}}'"
MIXED="\"Double \${{ENVIRONMENT}}\""
`;
const resolved = prepareEnvironmentVariables(serviceEnv, "");
expect(resolved).toEqual([
"ENVIRONMENT=production",
"QUOTED='production'",
'MIXED="Double production"',
]);
});
});
@@ -1,5 +1,6 @@
import { parseRawConfig, processLogs } from "@dokploy/server";
import { describe, expect, it } from "vitest";
const sampleLogEntry = `{"ClientAddr":"172.19.0.1:56732","ClientHost":"172.19.0.1","ClientPort":"56732","ClientUsername":"-","DownstreamContentSize":0,"DownstreamStatus":304,"Duration":14729375,"OriginContentSize":0,"OriginDuration":14051833,"OriginStatus":304,"Overhead":677542,"RequestAddr":"s222-umami-c381af.traefik.me","RequestContentSize":0,"RequestCount":122,"RequestHost":"s222-umami-c381af.traefik.me","RequestMethod":"GET","RequestPath":"/dashboard?_rsc=1rugv","RequestPort":"-","RequestProtocol":"HTTP/1.1","RequestScheme":"http","RetryAttempts":0,"RouterName":"s222-umami-60e104-47-web@docker","ServiceAddr":"10.0.1.15:3000","ServiceName":"s222-umami-60e104-47-web@docker","ServiceURL":{"Scheme":"http","Opaque":"","User":null,"Host":"10.0.1.15:3000","Path":"","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"StartLocal":"2024-08-25T04:34:37.306691884Z","StartUTC":"2024-08-25T04:34:37.306691884Z","entryPointName":"web","level":"info","msg":"","time":"2024-08-25T04:34:37Z"}`;
describe("processLogs", () => {
@@ -0,0 +1,102 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ApplicationNested } from "@dokploy/server/utils/builders";
import { mechanizeDockerContainer } from "@dokploy/server/utils/builders";
type MockCreateServiceOptions = {
StopGracePeriod?: number;
[key: string]: unknown;
};
const { inspectMock, getServiceMock, createServiceMock, getRemoteDockerMock } =
vi.hoisted(() => {
const inspect = vi.fn<[], Promise<never>>();
const getService = vi.fn(() => ({ inspect }));
const createService = vi.fn<[MockCreateServiceOptions], Promise<void>>(
async () => undefined,
);
const getRemoteDocker = vi.fn(async () => ({
getService,
createService,
}));
return {
inspectMock: inspect,
getServiceMock: getService,
createServiceMock: createService,
getRemoteDockerMock: getRemoteDocker,
};
});
vi.mock("@dokploy/server/utils/servers/remote-docker", () => ({
getRemoteDocker: getRemoteDockerMock,
}));
const createApplication = (
overrides: Partial<ApplicationNested> = {},
): ApplicationNested =>
({
appName: "test-app",
buildType: "dockerfile",
env: null,
mounts: [],
cpuLimit: null,
memoryLimit: null,
memoryReservation: null,
cpuReservation: null,
command: null,
ports: [],
sourceType: "docker",
dockerImage: "example:latest",
registry: null,
environment: {
project: { env: null },
env: null,
},
replicas: 1,
stopGracePeriodSwarm: 0n,
serverId: "server-id",
...overrides,
}) as unknown as ApplicationNested;
describe("mechanizeDockerContainer", () => {
beforeEach(() => {
inspectMock.mockReset();
inspectMock.mockRejectedValue(new Error("service not found"));
getServiceMock.mockClear();
createServiceMock.mockClear();
getRemoteDockerMock.mockClear();
getRemoteDockerMock.mockResolvedValue({
getService: getServiceMock,
createService: createServiceMock,
});
});
it("converts bigint stopGracePeriodSwarm to a number and keeps zero values", async () => {
const application = createApplication({ stopGracePeriodSwarm: 0n });
await mechanizeDockerContainer(application);
expect(createServiceMock).toHaveBeenCalledTimes(1);
const call = createServiceMock.mock.calls[0];
if (!call) {
throw new Error("createServiceMock should have been called once");
}
const [settings] = call;
expect(settings.StopGracePeriod).toBe(0);
expect(typeof settings.StopGracePeriod).toBe("number");
});
it("omits StopGracePeriod when stopGracePeriodSwarm is null", async () => {
const application = createApplication({ stopGracePeriodSwarm: null });
await mechanizeDockerContainer(application);
expect(createServiceMock).toHaveBeenCalledTimes(1);
const call = createServiceMock.mock.calls[0];
if (!call) {
throw new Error("createServiceMock should have been called once");
}
const [settings] = call;
expect(settings).not.toHaveProperty("StopGracePeriod");
});
});
+17 -7
View File
@@ -1,12 +1,12 @@
import type { Domain } from "@dokploy/server";
import type { Redirect } from "@dokploy/server";
import type { ApplicationNested } from "@dokploy/server";
import type { ApplicationNested, Domain, Redirect } from "@dokploy/server";
import { createRouterConfig } from "@dokploy/server";
import { expect, test } from "vitest";
const baseApp: ApplicationNested = {
railpackVersion: "0.2.2",
rollbackActive: false,
applicationId: "",
previewLabels: [],
herokuVersion: "",
giteaRepository: "",
giteaOwner: "",
@@ -18,6 +18,7 @@ const baseApp: ApplicationNested = {
appName: "",
autoDeploy: true,
enableSubmodules: false,
previewRequireCollaboratorPermissions: false,
serverId: "",
branch: null,
dockerBuildStage: "",
@@ -35,13 +36,22 @@ const baseApp: ApplicationNested = {
previewLimit: 0,
previewCustomCertResolver: null,
previewWildcard: "",
project: {
environmentId: "",
environment: {
env: "",
organizationId: "",
environmentId: "",
name: "",
description: "",
createdAt: "",
description: "",
projectId: "",
project: {
env: "",
organizationId: "",
name: "",
description: "",
createdAt: "",
projectId: "",
},
},
buildPath: "/",
gitlabPathNamespace: "",
@@ -84,7 +94,6 @@ const baseApp: ApplicationNested = {
password: null,
placementSwarm: null,
ports: [],
projectId: "",
publishDirectory: null,
isStaticSpa: null,
redirects: [],
@@ -102,6 +111,7 @@ const baseApp: ApplicationNested = {
updateConfigSwarm: null,
username: null,
dockerContextPath: null,
stopGracePeriodSwarm: null,
};
const baseDomain: Domain = {
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { HelpCircle, Settings } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
@@ -19,6 +25,7 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Tooltip,
TooltipContent,
@@ -26,12 +33,6 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { HelpCircle, Settings } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const HealthCheckSwarmSchema = z
.object({
@@ -176,26 +177,51 @@ const addSwarmSettings = z.object({
modeSwarm: createStringToJSONSchema(ServiceModeSwarmSchema).nullable(),
labelsSwarm: createStringToJSONSchema(LabelsSwarmSchema).nullable(),
networkSwarm: createStringToJSONSchema(NetworkSwarmSchema).nullable(),
stopGracePeriodSwarm: z.bigint().nullable(),
});
type AddSwarmSettings = z.infer<typeof addSwarmSettings>;
const hasStopGracePeriodSwarm = (
value: unknown,
): value is { stopGracePeriodSwarm: bigint | number | string | null } =>
typeof value === "object" &&
value !== null &&
"stopGracePeriodSwarm" in value;
interface Props {
applicationId: string;
id: string;
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
}
export const AddSwarmSettings = ({ applicationId }: Props) => {
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
},
{
enabled: !!applicationId,
},
);
export const AddSwarmSettings = ({ id, type }: Props) => {
const queryMap = {
postgres: () =>
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
mariadb: () =>
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
application: () =>
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
};
const { data, refetch } = queryMap[type]
? queryMap[type]()
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
const { mutateAsync, isError, error, isLoading } =
api.application.update.useMutation();
const mutationMap = {
postgres: () => api.postgres.update.useMutation(),
redis: () => api.redis.update.useMutation(),
mysql: () => api.mysql.update.useMutation(),
mariadb: () => api.mariadb.update.useMutation(),
application: () => api.application.update.useMutation(),
mongo: () => api.mongo.update.useMutation(),
};
const { mutateAsync, isError, error, isLoading } = mutationMap[type]
? mutationMap[type]()
: api.mongo.update.useMutation();
const form = useForm<AddSwarmSettings>({
defaultValues: {
@@ -207,12 +233,22 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
modeSwarm: null,
labelsSwarm: null,
networkSwarm: null,
stopGracePeriodSwarm: null,
},
resolver: zodResolver(addSwarmSettings),
});
useEffect(() => {
if (data) {
const stopGracePeriodValue = hasStopGracePeriodSwarm(data)
? data.stopGracePeriodSwarm
: null;
const normalizedStopGracePeriod =
stopGracePeriodValue === null || stopGracePeriodValue === undefined
? null
: typeof stopGracePeriodValue === "bigint"
? stopGracePeriodValue
: BigInt(stopGracePeriodValue);
form.reset({
healthCheckSwarm: data.healthCheckSwarm
? JSON.stringify(data.healthCheckSwarm, null, 2)
@@ -238,13 +274,19 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
networkSwarm: data.networkSwarm
? JSON.stringify(data.networkSwarm, null, 2)
: null,
stopGracePeriodSwarm: normalizedStopGracePeriod,
});
}
}, [form, form.reset, data]);
const onSubmit = async (data: AddSwarmSettings) => {
await mutateAsync({
applicationId,
applicationId: id || "",
postgresId: id || "",
redisId: id || "",
mysqlId: id || "",
mariadbId: id || "",
mongoId: id || "",
healthCheckSwarm: data.healthCheckSwarm,
restartPolicySwarm: data.restartPolicySwarm,
placementSwarm: data.placementSwarm,
@@ -253,6 +295,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
modeSwarm: data.modeSwarm,
labelsSwarm: data.labelsSwarm,
networkSwarm: data.networkSwarm,
stopGracePeriodSwarm: data.stopGracePeriodSwarm ?? null,
})
.then(async () => {
toast.success("Swarm settings updated");
@@ -270,7 +313,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
Swarm Settings
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-5xl p-0">
<DialogContent className="sm:max-w-5xl">
<DialogHeader>
<DialogTitle>Swarm Settings</DialogTitle>
<DialogDescription>
@@ -278,10 +321,10 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="px-4">
<div>
<AlertBlock type="info">
Changing settings such as placements may cause the logs/monitoring
to be unavailable.
Changing settings such as placements may cause the logs/monitoring,
backups and other features to be unavailable.
</AlertBlock>
</div>
@@ -289,13 +332,13 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
<form
id="hook-form-add-permissions"
onSubmit={form.handleSubmit(onSubmit)}
className="grid grid-cols-1 md:grid-cols-2 w-full gap-4 relative"
className="grid grid-cols-1 md:grid-cols-2 w-full gap-4 relative mt-4"
>
<FormField
control={form.control}
name="healthCheckSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
<FormItem className="relative ">
<FormLabel>Health Check</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
@@ -330,9 +373,9 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
language="json"
placeholder={`{
"Test" : ["CMD-SHELL", "curl -f http://localhost:3000/health"],
"Interval" : 10000,
"Timeout" : 10000,
"StartPeriod" : 10000,
"Interval" : 10000000000,
"Timeout" : 10000000000,
"StartPeriod" : 10000000000,
"Retries" : 10
}`}
className="h-[12rem] font-mono"
@@ -351,7 +394,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control}
name="restartPolicySwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
<FormItem className="relative ">
<FormLabel>Restart Policy</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
@@ -385,9 +428,9 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
language="json"
placeholder={`{
"Condition" : "on-failure",
"Delay" : 10000,
"Delay" : 10000000000,
"MaxAttempts" : 10,
"Window" : 10000
"Window" : 10000000000
} `}
className="h-[12rem] font-mono"
{...field}
@@ -405,7 +448,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control}
name="placementSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
<FormItem className="relative ">
<FormLabel>Placement</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
@@ -471,7 +514,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control}
name="updateConfigSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
<FormItem className="relative ">
<FormLabel>Update Config</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
@@ -507,9 +550,9 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
language="json"
placeholder={`{
"Parallelism" : 1,
"Delay" : 10000,
"Delay" : 10000000000,
"FailureAction" : "continue",
"Monitor" : 10000,
"Monitor" : 10000000000,
"MaxFailureRatio" : 10,
"Order" : "start-first"
}`}
@@ -529,7 +572,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control}
name="rollbackConfigSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
<FormItem className="relative ">
<FormLabel>Rollback Config</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
@@ -565,9 +608,9 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
language="json"
placeholder={`{
"Parallelism" : 1,
"Delay" : 10000,
"Delay" : 10000000000,
"FailureAction" : "continue",
"Monitor" : 10000,
"Monitor" : 10000000000,
"MaxFailureRatio" : 10,
"Order" : "start-first"
}`}
@@ -587,7 +630,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control}
name="modeSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
<FormItem className="relative ">
<FormLabel>Mode</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
@@ -650,7 +693,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control}
name="networkSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
<FormItem className="relative ">
<FormLabel>Network</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
@@ -709,7 +752,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
control={form.control}
name="labelsSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pr-6 ">
<FormItem className="relative ">
<FormLabel>Labels</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
@@ -752,7 +795,57 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="stopGracePeriodSwarm"
render={({ field }) => (
<FormItem className="relative max-lg:px-4 lg:pl-6 ">
<FormLabel>Stop Grace Period (nanoseconds)</FormLabel>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<FormDescription className="break-all w-fit flex flex-row gap-1 items-center">
Duration in nanoseconds
<HelpCircle className="size-4 text-muted-foreground" />
</FormDescription>
</TooltipTrigger>
<TooltipContent
className="w-full z-[999]"
align="start"
side="bottom"
>
<code>
<pre>
{`Enter duration in nanoseconds:
• 30000000000 - 30 seconds
• 120000000000 - 2 minutes
• 3600000000000 - 1 hour
• 0 - no grace period`}
</pre>
</code>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<FormControl>
<Input
type="number"
placeholder="30000000000"
className="font-mono"
{...field}
value={field?.value?.toString() || ""}
onChange={(e) =>
field.onChange(
e.target.value ? BigInt(e.target.value) : null,
)
}
/>
</FormControl>
<pre>
<FormMessage />
</pre>
</FormItem>
)}
/>
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2 m-0 sticky bottom-0 right-0 bg-muted border">
<Button
isLoading={isLoading}
@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Server } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -26,43 +33,57 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Server } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AddSwarmSettings } from "./modify-swarm-settings";
interface Props {
applicationId: string;
id: string;
type: "postgres" | "mariadb" | "mongo" | "mysql" | "redis" | "application";
}
const AddRedirectchema = z.object({
replicas: z.number().min(1, "Replicas must be at least 1"),
registryId: z.string(),
registryId: z.string().optional(),
});
type AddCommand = z.infer<typeof AddRedirectchema>;
export const ShowClusterSettings = ({ applicationId }: Props) => {
const { data } = api.application.one.useQuery(
{
applicationId,
},
{ enabled: !!applicationId },
);
export const ShowClusterSettings = ({ id, type }: Props) => {
const queryMap = {
postgres: () =>
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
mariadb: () =>
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
application: () =>
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
};
const { data, refetch } = queryMap[type]
? queryMap[type]()
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
const { data: registries } = api.registry.all.useQuery();
const utils = api.useUtils();
const mutationMap = {
postgres: () => api.postgres.update.useMutation(),
redis: () => api.redis.update.useMutation(),
mysql: () => api.mysql.update.useMutation(),
mariadb: () => api.mariadb.update.useMutation(),
application: () => api.application.update.useMutation(),
mongo: () => api.mongo.update.useMutation(),
};
const { mutateAsync, isLoading } = api.application.update.useMutation();
const { mutateAsync, isLoading } = mutationMap[type]
? mutationMap[type]()
: api.mongo.update.useMutation();
const form = useForm<AddCommand>({
defaultValues: {
registryId: data?.registryId || "",
...(type === "application" && data && "registryId" in data
? {
registryId: data?.registryId || "",
}
: {}),
replicas: data?.replicas || 1,
},
resolver: zodResolver(AddRedirectchema),
@@ -71,7 +92,11 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
useEffect(() => {
if (data?.command) {
form.reset({
registryId: data?.registryId || "",
...(type === "application" && data && "registryId" in data
? {
registryId: data?.registryId || "",
}
: {}),
replicas: data?.replicas || 1,
});
}
@@ -79,18 +104,25 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
const onSubmit = async (data: AddCommand) => {
await mutateAsync({
applicationId,
registryId:
data?.registryId === "none" || !data?.registryId
? null
: data?.registryId,
applicationId: id || "",
postgresId: id || "",
redisId: id || "",
mysqlId: id || "",
mariadbId: id || "",
mongoId: id || "",
...(type === "application"
? {
registryId:
data?.registryId === "none" || !data?.registryId
? null
: data?.registryId,
}
: {}),
replicas: data?.replicas,
})
.then(async () => {
toast.success("Command Updated");
await utils.application.one.invalidate({
applicationId,
});
await refetch();
})
.catch(() => {
toast.error("Error updating the command");
@@ -103,10 +135,10 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
<div>
<CardTitle className="text-xl">Cluster Settings</CardTitle>
<CardDescription>
Add the registry and the replicas of the application
Modify swarm settings for the service.
</CardDescription>
</div>
<AddSwarmSettings applicationId={applicationId} />
<AddSwarmSettings id={id} type={type} />
</CardHeader>
<CardContent className="flex flex-col gap-4">
<AlertBlock type="info">
@@ -144,58 +176,62 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
/>
</div>
{registries && registries?.length === 0 ? (
<div className="pt-10">
<div className="flex flex-col items-center gap-3">
<Server className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To use a cluster feature, you need to configure at least a
registry first. Please, go to{" "}
<Link
href="/dashboard/settings/cluster"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
</div>
) : (
{type === "application" && (
<>
<FormField
control={form.control}
name="registryId"
render={({ field }) => (
<FormItem>
<FormLabel>Select a registry</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<SelectTrigger>
<SelectValue placeholder="Select a registry" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{registries?.map((registry) => (
<SelectItem
key={registry.registryId}
value={registry.registryId}
>
{registry.registryName}
</SelectItem>
))}
<SelectItem value={"none"}>None</SelectItem>
<SelectLabel>
Registries ({registries?.length})
</SelectLabel>
</SelectGroup>
</SelectContent>
</Select>
</FormItem>
)}
/>
{registries && registries?.length === 0 ? (
<div className="pt-10">
<div className="flex flex-col items-center gap-3">
<Server className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To use a cluster feature, you need to configure at least
a registry first. Please, go to{" "}
<Link
href="/dashboard/settings/cluster"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
</div>
) : (
<>
<FormField
control={form.control}
name="registryId"
render={({ field }) => (
<FormItem>
<FormLabel>Select a registry</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<SelectTrigger>
<SelectValue placeholder="Select a registry" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{registries?.map((registry) => (
<SelectItem
key={registry.registryId}
value={registry.registryId}
>
{registry.registryName}
</SelectItem>
))}
<SelectItem value={"none"}>None</SelectItem>
<SelectLabel>
Registries ({registries?.length})
</SelectLabel>
</SelectGroup>
</SelectContent>
</Select>
</FormItem>
)}
/>
</>
)}
</>
)}
@@ -1,3 +1,8 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -16,11 +21,7 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
interface Props {
applicationId: string;
}
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Code2, Globe2, HardDrive } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
@@ -27,12 +33,6 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Code2, Globe2, HardDrive } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const ImportSchema = z.object({
base64: z.string(),
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon, PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm, useWatch } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -26,12 +32,6 @@ import {
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon, PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const AddPortSchema = z.object({
publishedPort: z.number().int().min(1).max(65535),
@@ -80,6 +80,11 @@ export const HandlePorts = ({
resolver: zodResolver(AddPortSchema),
});
const publishMode = useWatch({
control: form.control,
name: "publishMode",
});
useEffect(() => {
form.reset({
publishedPort: data?.publishedPort ?? 0,
@@ -253,6 +258,16 @@ export const HandlePorts = ({
</div>
</form>
{publishMode === "host" && (
<AlertBlock type="warning" className="mt-4">
<strong>Host Mode Limitation:</strong> When using Host publish
mode, Docker Swarm has limitations that prevent proper container
updates during deployments. Old containers may not be replaced
automatically. Consider using Ingress mode instead, or be prepared
to manually stop/start the application after deployments.
</AlertBlock>
)}
<DialogFooter>
<Button
isLoading={isLoading}
@@ -1,3 +1,5 @@
import { Rss, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { AlertBlock } from "@/components/shared/alert-block";
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
@@ -9,9 +11,8 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { Rss, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { HandlePorts } from "./handle-ports";
interface Props {
applicationId: string;
}
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon, PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -30,12 +36,6 @@ import {
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon, PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const AddRedirectchema = z.object({
regex: z.string().min(1, "Regex required"),
@@ -1,3 +1,5 @@
import { Split, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import {
@@ -8,8 +10,6 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { Split, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { HandleRedirect } from "./handle-redirect";
interface Props {
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon, PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -19,12 +25,6 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon, PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const AddSecuritychema = z.object({
username: z.string().min(1, "Username is required"),
@@ -151,7 +151,7 @@ export const HandleSecurity = ({
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input placeholder="test" {...field} />
<Input placeholder="test" type="password" {...field} />
</FormControl>
<FormMessage />
@@ -1,4 +1,7 @@
import { LockKeyhole, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { DialogAction } from "@/components/shared/dialog-action";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -7,9 +10,9 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { LockKeyhole, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { HandleSecurity } from "./handle-security";
interface Props {
@@ -58,19 +61,18 @@ export const ShowSecurity = ({ applicationId }: Props) => {
<div className="flex flex-col gap-6 ">
{data?.security.map((security) => (
<div key={security.securityId}>
<div className="flex w-full flex-col sm:flex-row justify-between sm:items-center gap-4 sm:gap-10 border rounded-lg p-4">
<div className="grid grid-cols-1 sm:grid-cols-2 flex-col gap-4 sm:gap-8">
<div className="flex flex-col gap-1">
<span className="font-medium">Username</span>
<span className="text-sm text-muted-foreground">
{security.username}
</span>
<div className="flex w-full flex-col md:flex-row justify-between md:items-center gap-4 md:gap-10 border rounded-lg p-4">
<div className="grid grid-cols-1 md:grid-cols-2 flex-col gap-4 md:gap-8">
<div className="flex flex-col gap-2">
<Label>Username</Label>
<Input disabled value={security.username} />
</div>
<div className="flex flex-col gap-1">
<span className="font-medium">Password</span>
<span className="text-sm text-muted-foreground">
{security.password}
</span>
<div className="flex flex-col gap-2">
<Label>Password</Label>
<ToggleVisibilityInput
value={security.password}
disabled
/>
</div>
</div>
<div className="flex flex-row gap-2">
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { InfoIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -23,12 +29,6 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { InfoIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const addResourcesSchema = z.object({
memoryReservation: z.string().optional(),
@@ -1,3 +1,4 @@
import { File, Loader2 } from "lucide-react";
import { CodeEditor } from "@/components/shared/code-editor";
import {
Card,
@@ -7,8 +8,8 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { File, Loader2 } from "lucide-react";
import { UpdateTraefikConfig } from "./update-traefik-config";
interface Props {
applicationId: string;
}
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { parse, stringify, YAMLParseError } from "yaml";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
@@ -19,12 +25,6 @@ import {
FormMessage,
} from "@/components/ui/form";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import jsyaml from "js-yaml";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const UpdateTraefikConfigSchema = z.object({
traefikConfig: z.string(),
@@ -38,11 +38,11 @@ interface Props {
export const validateAndFormatYAML = (yamlText: string) => {
try {
const obj = jsyaml.load(yamlText);
const formattedYaml = jsyaml.dump(obj, { indent: 4 });
const obj = parse(yamlText);
const formattedYaml = stringify(obj, { indent: 4 });
return { valid: true, formattedYaml, error: null };
} catch (error) {
if (error instanceof jsyaml.YAMLException) {
if (error instanceof YAMLParseError) {
return {
valid: false,
formattedYaml: yamlText,
@@ -89,7 +89,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
if (!valid) {
form.setError("traefikConfig", {
type: "manual",
message: error || "Invalid YAML",
message: (error as string) || "Invalid YAML",
});
return;
}
@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { PlusIcon } from "lucide-react";
import type React from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
@@ -22,13 +29,7 @@ import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PlusIcon } from "lucide-react";
import type React from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
interface Props {
serviceId: string;
serviceType:
@@ -1,3 +1,5 @@
import { Package, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { AlertBlock } from "@/components/shared/alert-block";
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
@@ -9,11 +11,10 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { Package, Trash2 } from "lucide-react";
import { toast } from "sonner";
import type { ServiceType } from "../show-resources";
import { AddVolumes } from "./add-volumes";
import { UpdateVolume } from "./update-volume";
interface Props {
id: string;
type: ServiceType | "compose";
@@ -80,7 +81,7 @@ export const ShowVolumes = ({ id, type }: Props) => {
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
>
{/* <Package className="size-8 self-center text-muted-foreground" /> */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 flex-col gap-4 sm:gap-8">
<div className="flex flex-col gap-1">
<span className="font-medium">Mount Type</span>
<span className="text-sm text-muted-foreground">
@@ -112,21 +113,21 @@ export const ShowVolumes = ({ id, type }: Props) => {
</span>
</div>
)}
{mount.type === "file" ? (
{mount.type === "file" && (
<div className="flex flex-col gap-1">
<span className="font-medium">File Path</span>
<span className="text-sm text-muted-foreground">
{mount.filePath}
</span>
</div>
) : (
<div className="flex flex-col gap-1">
<span className="font-medium">Mount Path</span>
<span className="text-sm text-muted-foreground">
{mount.mountPath}
</span>
</div>
)}
<div className="flex flex-col gap-1">
<span className="font-medium">Mount Path</span>
<span className="text-sm text-muted-foreground">
{mount.mountPath}
</span>
</div>
</div>
<div className="flex flex-row gap-1">
<UpdateVolume
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
@@ -20,12 +26,6 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const mountSchema = z.object({
mountPath: z.string().min(1, "Mount path required"),
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Cog } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -15,12 +21,6 @@ import {
import { Input } from "@/components/ui/input";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Cog } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
export enum BuildType {
dockerfile = "dockerfile",
@@ -65,6 +65,7 @@ const mySchema = z.discriminatedUnion("buildType", [
}),
z.object({
buildType: z.literal(BuildType.railpack),
railpackVersion: z.string().nullable().default("0.2.2"),
}),
z.object({
buildType: z.literal(BuildType.static),
@@ -86,6 +87,7 @@ interface ApplicationData {
herokuVersion?: string | null;
publishDirectory?: string | null;
isStaticSpa?: boolean | null;
railpackVersion?: string | null | undefined;
}
function isValidBuildType(value: string): value is BuildType {
@@ -123,6 +125,7 @@ const resetData = (data: ApplicationData): AddTemplate => {
case BuildType.railpack:
return {
buildType: BuildType.railpack,
railpackVersion: data.railpackVersion || null,
};
default: {
const buildType = data.buildType as BuildType;
@@ -181,6 +184,10 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
: null,
isStaticSpa:
data.buildType === BuildType.static ? data.isStaticSpa : null,
railpackVersion:
data.buildType === BuildType.railpack
? data.railpackVersion || "0.2.2"
: null,
})
.then(async () => {
toast.success("Build type saved");
@@ -395,6 +402,25 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
)}
/>
)}
{buildType === BuildType.railpack && (
<FormField
control={form.control}
name="railpackVersion"
render={({ field }) => (
<FormItem>
<FormLabel>Railpack Version</FormLabel>
<FormControl>
<Input
placeholder="Railpack Version"
{...field}
value={field.value ?? ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<div className="flex w-full justify-end">
<Button isLoading={isLoading} type="submit">
Save
@@ -1,3 +1,5 @@
import { Paintbrush } from "lucide-react";
import { toast } from "sonner";
import {
AlertDialog,
AlertDialogAction,
@@ -11,8 +13,6 @@ import {
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { Paintbrush } from "lucide-react";
import { toast } from "sonner";
interface Props {
id: string;
@@ -1,3 +1,5 @@
import { RefreshCcw } from "lucide-react";
import { toast } from "sonner";
import {
AlertDialog,
AlertDialogAction,
@@ -10,8 +12,6 @@ import {
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { api } from "@/utils/api";
import { RefreshCcw } from "lucide-react";
import { toast } from "sonner";
interface Props {
id: string;
@@ -1,3 +1,5 @@
import { Loader2 } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import {
@@ -7,8 +9,6 @@ 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";
@@ -1,8 +1,7 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import type { RouterOutputs } from "@/utils/api";
import { useState } from "react";
import { ShowDeployment } from "../deployments/show-deployment";
import { ShowDeployments } from "./show-deployments";
@@ -1,4 +1,9 @@
import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react";
import React, { useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import { AlertBlock } from "@/components/shared/alert-block";
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";
@@ -9,15 +14,11 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { type RouterOutputs, api } from "@/utils/api";
import { Clock, Loader2, RocketIcon, Settings, RefreshCcw } from "lucide-react";
import React, { useEffect, useState } from "react";
import { api, type RouterOutputs } from "@/utils/api";
import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
import { CancelQueues } from "./cancel-queues";
import { RefreshToken } from "./refresh-token";
import { ShowDeployment } from "./show-deployment";
import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
import { DialogAction } from "@/components/shared/dialog-action";
import { toast } from "sonner";
interface Props {
id: string;
@@ -61,12 +62,48 @@ export const ShowDeployments = ({
},
);
const { data: isCloud } = api.settings.isCloud.useQuery();
const { mutateAsync: rollback, isLoading: isRollingBack } =
api.rollback.rollback.useMutation();
const { mutateAsync: killProcess, isLoading: isKillingProcess } =
api.deployment.killProcess.useMutation();
// Cancel deployment mutations
const {
mutateAsync: cancelApplicationDeployment,
isLoading: isCancellingApp,
} = api.application.cancelDeployment.useMutation();
const {
mutateAsync: cancelComposeDeployment,
isLoading: isCancellingCompose,
} = api.compose.cancelDeployment.useMutation();
const [url, setUrl] = React.useState("");
// Check for stuck deployment (more than 9 minutes) - only for the most recent deployment
const stuckDeployment = useMemo(() => {
if (!isCloud || !deployments || deployments.length === 0) return null;
const now = Date.now();
const NINE_MINUTES = 10 * 60 * 1000; // 9 minutes in milliseconds
// Get the most recent deployment (first in the list since they're sorted by date)
const mostRecentDeployment = deployments[0];
if (
!mostRecentDeployment ||
mostRecentDeployment.status !== "running" ||
!mostRecentDeployment.startedAt
) {
return null;
}
const startTime = new Date(mostRecentDeployment.startedAt).getTime();
const elapsed = now - startTime;
return elapsed > NINE_MINUTES ? mostRecentDeployment : null;
}, [isCloud, deployments]);
useEffect(() => {
setUrl(document.location.origin);
}, []);
@@ -77,7 +114,7 @@ export const ShowDeployments = ({
<div className="flex flex-col gap-2">
<CardTitle className="text-xl">Deployments</CardTitle>
<CardDescription>
See all the 10 last deployments for this {type}
See the last 10 deployments for this {type}
</CardDescription>
</div>
<div className="flex flex-row items-center gap-2">
@@ -94,6 +131,54 @@ export const ShowDeployments = ({
</div>
</CardHeader>
<CardContent className="flex flex-col gap-4">
{stuckDeployment && (type === "application" || type === "compose") && (
<AlertBlock
type="warning"
className="flex-col items-start w-full p-4"
>
<div className="flex flex-col gap-3">
<div>
<div className="font-medium text-sm mb-1">
Build appears to be stuck
</div>
<p className="text-sm">
Hey! Looks like the build has been running for more than 10
minutes. Would you like to cancel this deployment?
</p>
</div>
<Button
variant="destructive"
size="sm"
className="w-fit"
isLoading={
type === "application" ? isCancellingApp : isCancellingCompose
}
onClick={async () => {
try {
if (type === "application") {
await cancelApplicationDeployment({
applicationId: id,
});
} else if (type === "compose") {
await cancelComposeDeployment({
composeId: id,
});
}
toast.success("Deployment cancellation requested");
} catch (error) {
toast.error(
error instanceof Error
? error.message
: "Failed to cancel deployment",
);
}
}}
>
Cancel Deployment
</Button>
</div>
</AlertBlock>
)}
{refreshToken && (
<div className="flex flex-col gap-2 text-sm">
<span>
@@ -104,7 +189,9 @@ export const ShowDeployments = ({
<span>Webhook URL: </span>
<div className="flex flex-row items-center gap-2">
<span className="break-all text-muted-foreground">
{`${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`}
{`${url}/api/deploy${
type === "compose" ? "/compose" : ""
}/${refreshToken}`}
</span>
{(type === "application" || type === "compose") && (
<RefreshToken id={id} type={type} />
@@ -1,3 +1,5 @@
import { Copy, HelpCircle, Server } from "lucide-react";
import { toast } from "sonner";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -8,8 +10,6 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Copy, HelpCircle, Server } from "lucide-react";
import { toast } from "sonner";
interface Props {
domain: {
@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { DatabaseZap, Dices, RefreshCw } from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import z from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -34,14 +41,6 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { zodResolver } from "@hookform/resolvers/zod";
import { DatabaseZap, Dices, RefreshCw } from "lucide-react";
import Link from "next/link";
import z from "zod";
export type CacheType = "fetch" | "cache";
@@ -123,6 +122,7 @@ interface Props {
export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [cacheType, setCacheType] = useState<CacheType>("cache");
const [isManualInput, setIsManualInput] = useState(false);
const utils = api.useUtils();
const { data, refetch } = api.domain.one.useQuery(
@@ -325,46 +325,126 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
<FormItem className="w-full">
<FormLabel>Service Name</FormLabel>
<div className="flex gap-2">
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
{isManualInput ? (
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a service name" />
</SelectTrigger>
<Input
placeholder="Enter service name manually"
{...field}
className="w-full"
/>
</FormControl>
) : (
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a service name" />
</SelectTrigger>
</FormControl>
<SelectContent>
{services?.map((service, index) => (
<SelectItem
value={service}
key={`${service}-${index}`}
>
{service}
<SelectContent>
{services?.map((service, index) => (
<SelectItem
value={service}
key={`${service}-${index}`}
>
{service}
</SelectItem>
))}
<SelectItem value="none" disabled>
Empty
</SelectItem>
))}
<SelectItem value="none" disabled>
Empty
</SelectItem>
</SelectContent>
</Select>
</SelectContent>
</Select>
)}
{!isManualInput && (
<>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
type="button"
isLoading={isLoadingServices}
onClick={() => {
if (cacheType === "fetch") {
refetchServices();
} else {
setCacheType("fetch");
}
}}
>
<RefreshCw className="size-4 text-muted-foreground" />
</Button>
</TooltipTrigger>
<TooltipContent
side="left"
sideOffset={5}
className="max-w-[10rem]"
>
<p>
Fetch: Will clone the repository and
load the services
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
type="button"
isLoading={isLoadingServices}
onClick={() => {
if (cacheType === "cache") {
refetchServices();
} else {
setCacheType("cache");
}
}}
>
<DatabaseZap className="size-4 text-muted-foreground" />
</Button>
</TooltipTrigger>
<TooltipContent
side="left"
sideOffset={5}
className="max-w-[10rem]"
>
<p>
Cache: If you previously deployed this
compose, it will read the services
from the last deployment/fetch from
the repository
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</>
)}
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
type="button"
isLoading={isLoadingServices}
onClick={() => {
if (cacheType === "fetch") {
refetchServices();
} else {
setCacheType("fetch");
setIsManualInput(!isManualInput);
if (!isManualInput) {
field.onChange("");
}
}}
>
<RefreshCw className="size-4 text-muted-foreground" />
{isManualInput ? (
<RefreshCw className="size-4 text-muted-foreground" />
) : (
<span className="text-xs text-muted-foreground">
Manual
</span>
)}
</Button>
</TooltipTrigger>
<TooltipContent
@@ -373,40 +453,9 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
className="max-w-[10rem]"
>
<p>
Fetch: Will clone the repository and load
the services
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
type="button"
isLoading={isLoadingServices}
onClick={() => {
if (cacheType === "cache") {
refetchServices();
} else {
setCacheType("cache");
}
}}
>
<DatabaseZap className="size-4 text-muted-foreground" />
</Button>
</TooltipTrigger>
<TooltipContent
side="left"
sideOffset={5}
className="max-w-[10rem]"
>
<p>
Cache: If you previously deployed this
compose, it will read the services from
the last deployment/fetch from the
repository
{isManualInput
? "Switch to service selection"
: "Enter service name manually"}
</p>
</TooltipContent>
</Tooltip>
@@ -1,3 +1,18 @@
import {
CheckCircle2,
ExternalLink,
GlobeIcon,
InfoIcon,
Loader2,
PenBoxIcon,
RefreshCw,
Server,
Trash2,
XCircle,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { toast } from "sonner";
import { DialogAction } from "@/components/shared/dialog-action";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -15,21 +30,6 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import {
CheckCircle2,
ExternalLink,
GlobeIcon,
InfoIcon,
Loader2,
PenBoxIcon,
RefreshCw,
Server,
Trash2,
XCircle,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { toast } from "sonner";
import { DnsHelperModal } from "./dns-helper-modal";
import { AddDomain } from "./handle-domain";
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { EyeIcon, EyeOffIcon } from "lucide-react";
import { type CSSProperties, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
@@ -16,12 +22,6 @@ import {
} from "@/components/ui/form";
import { Toggle } from "@/components/ui/toggle";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { EyeIcon, EyeOffIcon } from "lucide-react";
import { type CSSProperties, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import type { ServiceType } from "../advanced/show-resources";
const addEnvironmentSchema = z.object({
@@ -1,13 +1,13 @@
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Form } from "@/components/ui/form";
import { Secrets } from "@/components/ui/secrets";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Form } from "@/components/ui/form";
import { Secrets } from "@/components/ui/secrets";
import { api } from "@/utils/api";
const addEnvironmentSchema = z.object({
env: z.string(),
@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
@@ -40,13 +47,6 @@ import {
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const BitbucketProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
@@ -1,3 +1,8 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
Form,
@@ -9,11 +14,6 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
dockerImage: z.string().min(1, {
@@ -1,3 +1,8 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Dropzone } from "@/components/ui/dropzone";
import {
@@ -11,11 +16,6 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { type UploadFile, uploadFileSchema } from "@/utils/schema";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
interface Props {
applicationId: string;
@@ -1,3 +1,13 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { GitIcon } from "@/components/icons/data-tools-icons";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Form,
@@ -25,17 +35,6 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { GitIcon } from "@/components/icons/data-tools-icons";
import { Badge } from "@/components/ui/badge";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const GitProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
@@ -60,7 +59,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
const router = useRouter();
const { mutateAsync, isLoading } =
api.application.saveGitProdiver.useMutation();
api.application.saveGitProvider.useMutation();
const form = useForm<GitProvider>({
defaultValues: {
@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { GiteaIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
@@ -40,13 +47,6 @@ import {
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
interface GiteaRepository {
name: string;
@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { GithubIcon } from "@/components/icons/data-tools-icons";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -39,13 +46,6 @@ import {
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const GithubProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link";
import { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { GitlabIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
@@ -40,13 +47,6 @@ import {
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link";
import { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const GitlabProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
@@ -1,3 +1,7 @@
import { GitBranch, Loader2, UploadCloud } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { toast } from "sonner";
import { SaveDockerProvider } from "@/components/dashboard/application/general/generic/save-docker-provider";
import { SaveGitProvider } from "@/components/dashboard/application/general/generic/save-git-provider";
import { SaveGiteaProvider } from "@/components/dashboard/application/general/generic/save-gitea-provider";
@@ -5,18 +9,14 @@ import { SaveGithubProvider } from "@/components/dashboard/application/general/g
import {
BitbucketIcon,
DockerIcon,
GitIcon,
GiteaIcon,
GithubIcon,
GitIcon,
GitlabIcon,
} from "@/components/icons/data-tools-icons";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { api } from "@/utils/api";
import { GitBranch, Loader2, UploadCloud } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { toast } from "sonner";
import { SaveBitbucketProvider } from "./save-bitbucket-provider";
import { SaveDragNDrop } from "./save-drag-n-drop";
import { SaveGitlabProvider } from "./save-gitlab-provider";
@@ -1,8 +1,9 @@
import { AlertCircle, GitBranch, Unlink } from "lucide-react";
import {
BitbucketIcon,
GitIcon,
GiteaIcon,
GithubIcon,
GitIcon,
GitlabIcon,
} from "@/components/icons/data-tools-icons";
import { DialogAction } from "@/components/shared/dialog-action";
@@ -10,7 +11,6 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import type { RouterOutputs } from "@/utils/api";
import { AlertCircle, GitBranch, Unlink } from "lucide-react";
interface Props {
service:
@@ -1,3 +1,14 @@
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,
Hammer,
RefreshCcw,
Rocket,
Terminal,
} from "lucide-react";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { ShowBuildChooseForm } from "@/components/dashboard/application/build/show";
import { ShowProviderForm } from "@/components/dashboard/application/general/generic/show";
import { DialogAction } from "@/components/shared/dialog-action";
@@ -11,18 +22,8 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,
Hammer,
RefreshCcw,
Rocket,
Terminal,
} from "lucide-react";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
interface Props {
applicationId: string;
}
@@ -68,7 +69,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
toast.success("Application deployed successfully");
refetch();
router.push(
`/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`,
`/dashboard/project/${data?.environment.projectId}/environment/${data?.environmentId}/services/application/${applicationId}?tab=deployments`,
);
})
.catch(() => {
@@ -1,3 +1,6 @@
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
import { Badge } from "@/components/ui/badge";
import {
Card,
@@ -18,9 +21,6 @@ import {
} 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(
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Dices } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import type z from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -33,15 +39,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { domain } from "@/server/db/validations/domain";
import { zodResolver } from "@hookform/resolvers/zod";
import { Dices } from "lucide-react";
import type z from "zod";
import { api } from "@/utils/api";
type Domain = z.infer<typeof domain>;
@@ -1,3 +1,13 @@
import {
ExternalLink,
FileText,
GitPullRequest,
Loader2,
PenSquare,
RocketIcon,
Trash2,
} from "lucide-react";
import { toast } from "sonner";
import { GithubIcon } from "@/components/icons/data-tools-icons";
import { DateTooltip } from "@/components/shared/date-tooltip";
import { DialogAction } from "@/components/shared/dialog-action";
@@ -13,16 +23,6 @@ import {
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import {
ExternalLink,
FileText,
GitPullRequest,
Loader2,
PenSquare,
RocketIcon,
Trash2,
} from "lucide-react";
import { toast } from "sonner";
import { ShowModalLogs } from "../../settings/web-server/show-modal-logs";
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
import { AddPreviewDomain } from "./add-preview-domain";
@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { HelpCircle, Plus, Settings2, X } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -27,13 +34,13 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
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({
@@ -42,10 +49,12 @@ const schema = z
wildcardDomain: z.string(),
port: z.number(),
previewLimit: z.number(),
previewLabels: z.array(z.string()).optional(),
previewHttps: z.boolean(),
previewPath: z.string(),
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
previewCustomCertResolver: z.string().optional(),
previewRequireCollaboratorPermissions: z.boolean(),
})
.superRefine((input, ctx) => {
if (
@@ -80,9 +89,11 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
wildcardDomain: "*.traefik.me",
port: 3000,
previewLimit: 3,
previewLabels: [],
previewHttps: false,
previewPath: "/",
previewCertificateType: "none",
previewRequireCollaboratorPermissions: true,
},
resolver: zodResolver(schema),
});
@@ -100,11 +111,14 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
buildArgs: data.previewBuildArgs || "",
wildcardDomain: data.previewWildcard || "*.traefik.me",
port: data.previewPort || 3000,
previewLabels: data.previewLabels || [],
previewLimit: data.previewLimit || 3,
previewHttps: data.previewHttps || false,
previewPath: data.previewPath || "/",
previewCertificateType: data.previewCertificateType || "none",
previewCustomCertResolver: data.previewCustomCertResolver || "",
previewRequireCollaboratorPermissions:
data.previewRequireCollaboratorPermissions || true,
});
}
}, [data]);
@@ -115,12 +129,15 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewBuildArgs: formData.buildArgs,
previewWildcard: formData.wildcardDomain,
previewPort: formData.port,
previewLabels: formData.previewLabels,
applicationId,
previewLimit: formData.previewLimit,
previewHttps: formData.previewHttps,
previewPath: formData.previewPath,
previewCertificateType: formData.previewCertificateType,
previewCustomCertResolver: formData.previewCustomCertResolver,
previewRequireCollaboratorPermissions:
formData.previewRequireCollaboratorPermissions,
})
.then(() => {
toast.success("Preview Deployments settings updated");
@@ -194,6 +211,90 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="previewLabels"
render={({ field }) => (
<FormItem className="md:col-span-2">
<div className="flex items-center gap-2">
<FormLabel>Preview Labels</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipContent>
<p>
Add a labels that will trigger a preview
deployment for a pull request. If no labels
are specified, all pull requests will trigger
a preview deployment.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="flex flex-wrap gap-2 mb-2">
{field.value?.map((label, index) => (
<Badge
key={index}
variant="secondary"
className="flex items-center gap-1"
>
{label}
<X
className="size-3 cursor-pointer hover:text-destructive"
onClick={() => {
const newLabels = [...(field.value || [])];
newLabels.splice(index, 1);
field.onChange(newLabels);
}}
/>
</Badge>
))}
</div>
<div className="flex gap-2">
<FormControl>
<Input
placeholder="Enter a label (e.g. enhancements, needs-review)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
const input = e.currentTarget;
const label = input.value.trim();
if (label) {
field.onChange([
...(field.value || []),
label,
]);
input.value = "";
}
}
}}
/>
</FormControl>
<Button
type="button"
variant="outline"
size="icon"
onClick={() => {
const input = document.querySelector(
'input[placeholder*="Enter a label"]',
) as HTMLInputElement;
const label = input.value.trim();
if (label) {
field.onChange([...(field.value || []), label]);
input.value = "";
}
}}
>
<Plus className="size-4" />
</Button>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="previewLimit"
@@ -312,6 +413,37 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
</div>
</div>
<div className="grid gap-4 lg:grid-cols-2">
<FormField
control={form.control}
name="previewRequireCollaboratorPermissions"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm col-span-2">
<div className="space-y-0.5">
<FormLabel>
Require Collaborator Permissions
</FormLabel>
<FormDescription>
Require collaborator permissions to preview
deployments, valid roles are:
<ul>
<li>Admin</li>
<li>Maintain</li>
<li>Write</li>
</ul>
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="env"
@@ -1,3 +1,8 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -18,11 +23,6 @@ import {
} from "@/components/ui/form";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const formSchema = z.object({
rollbackActive: z.boolean(),
@@ -1,3 +1,15 @@
import { zodResolver } from "@hookform/resolvers/zod";
import {
DatabaseZap,
Info,
PenBoxIcon,
PlusCircle,
RefreshCw,
} from "lucide-react";
import { useEffect, useState } from "react";
import { type Control, useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
@@ -35,18 +47,6 @@ import {
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import {
DatabaseZap,
Info,
PenBoxIcon,
PlusCircle,
RefreshCw,
} from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import type { CacheType } from "../domains/handle-domain";
export const commonCronExpressions = [
@@ -57,6 +57,7 @@ export const commonCronExpressions = [
{ label: "Every month on the 1st at midnight", value: "0 0 1 * *" },
{ label: "Every 15 minutes", value: "*/15 * * * *" },
{ label: "Every weekday at midnight", value: "0 0 * * 1-5" },
{ label: "Custom", value: "custom" },
];
const formSchema = z
@@ -115,10 +116,91 @@ interface Props {
scheduleType?: "application" | "compose" | "server" | "dokploy-server";
}
export const ScheduleFormField = ({
name,
formControl,
}: {
name: string;
formControl: Control<any>;
}) => {
const [selectedOption, setSelectedOption] = useState("");
return (
<FormField
control={formControl}
name={name}
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center gap-2">
Schedule
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Info className="w-4 h-4 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>Cron expression format: minute hour day month weekday</p>
<p>Example: 0 0 * * * (daily at midnight)</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</FormLabel>
<div className="flex flex-col gap-2">
<Select
value={selectedOption}
onValueChange={(value) => {
setSelectedOption(value);
field.onChange(value === "custom" ? "" : value);
}}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a predefined schedule" />
</SelectTrigger>
</FormControl>
<SelectContent>
{commonCronExpressions.map((expr) => (
<SelectItem key={expr.value} value={expr.value}>
{expr.label}
{expr.value !== "custom" && ` (${expr.value})`}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="relative">
<FormControl>
<Input
placeholder="Custom cron expression (e.g., 0 0 * * *)"
{...field}
onChange={(e) => {
const value = e.target.value;
const commonExpression = commonCronExpressions.find(
(expression) => expression.value === value,
);
if (commonExpression) {
setSelectedOption(commonExpression.value);
} else {
setSelectedOption("custom");
}
field.onChange(e);
}}
/>
</FormControl>
</div>
</div>
<FormDescription>
Choose a predefined schedule or enter a custom cron expression
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
);
};
export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [cacheType, setCacheType] = useState<CacheType>("cache");
const utils = api.useUtils();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
@@ -377,63 +459,9 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
)}
/>
<FormField
control={form.control}
<ScheduleFormField
name="cronExpression"
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center gap-2">
Schedule
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Info className="w-4 h-4 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>
Cron expression format: minute hour day month
weekday
</p>
<p>Example: 0 0 * * * (daily at midnight)</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</FormLabel>
<div className="flex flex-col gap-2">
<Select
onValueChange={(value) => {
field.onChange(value);
}}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a predefined schedule" />
</SelectTrigger>
</FormControl>
<SelectContent>
{commonCronExpressions.map((expr) => (
<SelectItem key={expr.value} value={expr.value}>
{expr.label} ({expr.value})
</SelectItem>
))}
</SelectContent>
</Select>
<div className="relative">
<FormControl>
<Input
placeholder="Custom cron expression (e.g., 0 0 * * *)"
{...field}
/>
</FormControl>
</div>
</div>
<FormDescription>
Choose a predefined schedule or enter a custom cron
expression
</FormDescription>
<FormMessage />
</FormItem>
)}
formControl={form.control}
/>
{(scheduleTypeForm === "application" ||
@@ -1,3 +1,12 @@
import {
ClipboardList,
Clock,
Loader2,
Play,
Terminal,
Trash2,
} from "lucide-react";
import { toast } from "sonner";
import { DialogAction } from "@/components/shared/dialog-action";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -15,15 +24,6 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import {
ClipboardList,
Clock,
Loader2,
Play,
Terminal,
Trash2,
} from "lucide-react";
import { toast } from "sonner";
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
import { HandleSchedules } from "./handle-schedules";
@@ -58,7 +58,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
return (
<Card className="border px-6 shadow-none bg-transparent h-full min-h-[50vh]">
<CardHeader className="px-0">
<div className="flex justify-between items-center">
<div className="flex justify-between items-center gap-y-2 flex-wrap">
<div className="flex flex-col gap-2">
<CardTitle className="text-xl font-bold flex items-center gap-2">
Scheduled Tasks
@@ -91,15 +91,15 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
return (
<div
key={schedule.scheduleId}
className="flex items-center justify-between rounded-lg border p-3 transition-colors bg-muted/50"
className="flex items-center flex-wrap sm:flex-nowrap gap-y-2 justify-between rounded-lg border p-3 transition-colors bg-muted/50"
>
<div className="flex items-start gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5">
<div className="flex flex-shrink-0 h-9 w-9 items-center justify-center rounded-full bg-primary/5">
<Clock className="size-4 text-primary/70" />
</div>
<div className="space-y-1.5">
<div className="flex items-center gap-2">
<h3 className="text-sm font-medium leading-none">
<div className="flex items-center gap-2 flex-wrap">
<h3 className="text-sm font-medium leading-none [overflow-wrap:anywhere] line-clamp-3">
{schedule.name}
</h3>
<Badge
@@ -109,7 +109,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
{schedule.enabled ? "Enabled" : "Disabled"}
</Badge>
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<div className="flex items-center gap-2 text-sm text-muted-foreground flex-wrap">
<Badge
variant="outline"
className="font-mono text-[10px] bg-transparent"
@@ -142,7 +142,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
</div>
</div>
<div className="flex items-center gap-1.5">
<div className="flex items-center gap-0.5 md:gap-1.5">
<ShowDeploymentsModal
id={schedule.scheduleId}
type="schedule"
@@ -226,7 +226,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
})}
</div>
) : (
<div className="flex flex-col gap-2 items-center justify-center py-12 rounded-lg">
<div className="flex flex-col gap-2 items-center justify-center py-12 rounded-lg">
<Clock className="size-8 mb-4 text-muted-foreground" />
<p className="text-lg font-medium text-muted-foreground">
No scheduled tasks
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -20,12 +26,6 @@ import {
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const updateApplicationSchema = z.object({
name: z.string().min(1, {
@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { DatabaseZap, PenBoxIcon, PlusCircle, RefreshCw } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -34,20 +40,8 @@ import {
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import {
DatabaseZap,
Info,
PenBoxIcon,
PlusCircle,
RefreshCw,
} from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import type { CacheType } from "../domains/handle-domain";
import { commonCronExpressions } from "../schedules/handle-schedules";
import { ScheduleFormField } from "../schedules/handle-schedules";
const formSchema = z
.object({
@@ -55,7 +49,12 @@ const formSchema = z
cronExpression: z.string().min(1, "Cron expression is required"),
volumeName: z.string().min(1, "Volume name is required"),
prefix: z.string(),
// keepLatestCount: z.coerce.number().optional(),
keepLatestCount: z.coerce
.number()
.int()
.gte(1, "Must be at least 1")
.optional()
.nullable(),
turnOff: z.boolean().default(false),
enabled: z.boolean().default(true),
serviceType: z.enum([
@@ -108,6 +107,7 @@ export const HandleVolumeBackups = ({
}: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [cacheType, setCacheType] = useState<CacheType>("cache");
const [keepLatestCountInput, setKeepLatestCountInput] = useState("");
const utils = api.useUtils();
const form = useForm<z.infer<typeof formSchema>>({
@@ -117,7 +117,7 @@ export const HandleVolumeBackups = ({
cronExpression: "",
volumeName: "",
prefix: "",
// keepLatestCount: undefined,
keepLatestCount: undefined,
turnOff: false,
enabled: true,
serviceName: "",
@@ -173,13 +173,19 @@ export const HandleVolumeBackups = ({
cronExpression: volumeBackup.cronExpression,
volumeName: volumeBackup.volumeName || "",
prefix: volumeBackup.prefix,
// keepLatestCount: volumeBackup.keepLatestCount || undefined,
keepLatestCount: volumeBackup.keepLatestCount || undefined,
turnOff: volumeBackup.turnOff,
enabled: volumeBackup.enabled || false,
serviceName: volumeBackup.serviceName || "",
destinationId: volumeBackup.destinationId,
serviceType: volumeBackup.serviceType,
});
setKeepLatestCountInput(
volumeBackup.keepLatestCount !== null &&
volumeBackup.keepLatestCount !== undefined
? String(volumeBackup.keepLatestCount)
: "",
);
}
}, [form, volumeBackup, volumeBackupId]);
@@ -190,8 +196,12 @@ export const HandleVolumeBackups = ({
const onSubmit = async (values: z.infer<typeof formSchema>) => {
if (!id && !volumeBackupId) return;
const preparedKeepLatestCount =
keepLatestCountInput === "" ? null : (values.keepLatestCount ?? null);
await mutateAsync({
...values,
keepLatestCount: preparedKeepLatestCount,
destinationId: values.destinationId,
volumeBackupId: volumeBackupId || "",
serviceType: volumeBackupType,
@@ -257,9 +267,8 @@ export const HandleVolumeBackups = ({
</DialogTrigger>
<DialogContent
className={cn(
"overflow-y-auto",
volumeBackupType === "compose" || volumeBackupType === "application"
? "max-h-[95vh] sm:max-w-2xl"
? "sm:max-w-2xl"
: " sm:max-w-lg",
)}
>
@@ -291,64 +300,9 @@ export const HandleVolumeBackups = ({
</FormItem>
)}
/>
<FormField
control={form.control}
<ScheduleFormField
name="cronExpression"
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center gap-2">
Schedule
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Info className="w-4 h-4 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>
Cron expression format: minute hour day month
weekday
</p>
<p>Example: 0 0 * * * (daily at midnight)</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</FormLabel>
<div className="flex flex-col gap-2">
<Select
onValueChange={(value) => {
field.onChange(value);
}}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a predefined schedule" />
</SelectTrigger>
</FormControl>
<SelectContent>
{commonCronExpressions.map((expr) => (
<SelectItem key={expr.value} value={expr.value}>
{expr.label} ({expr.value})
</SelectItem>
))}
</SelectContent>
</Select>
<div className="relative">
<FormControl>
<Input
placeholder="Custom cron expression (e.g., 0 0 * * *)"
{...field}
/>
</FormControl>
</div>
</div>
<FormDescription>
Choose a predefined schedule or enter a custom cron
expression
</FormDescription>
<FormMessage />
</FormItem>
)}
formControl={form.control}
/>
<FormField
@@ -600,29 +554,38 @@ export const HandleVolumeBackups = ({
)}
/>
{/* <FormField
<FormField
control={form.control}
name="keepLatestCount"
render={({ field }) => (
<FormItem>
<FormLabel>Keep Latest Count</FormLabel>
<FormLabel>Keep Latest Backups</FormLabel>
<FormControl>
<Input
type="number"
placeholder="5"
{...field}
onChange={(e) =>
field.onChange(Number(e.target.value) || undefined)
}
type="number"
min={1}
autoComplete="off"
placeholder="Leave empty to keep all"
value={keepLatestCountInput}
onChange={(e) => {
const raw = e.target.value;
setKeepLatestCountInput(raw);
if (raw === "") {
field.onChange(undefined);
} else if (/^\d+$/.test(raw)) {
field.onChange(Number(raw));
}
}}
/>
</FormControl>
<FormDescription>
Number of backup files to keep (optional)
How many recent backups to keep. Empty means no cleanup.
</FormDescription>
<FormMessage />
</FormItem>
)}
/> */}
/>
<FormField
control={form.control}
@@ -1,3 +1,12 @@
import { zodResolver } from "@hookform/resolvers/zod";
import copy from "copy-to-clipboard";
import { debounce } from "lodash";
import { CheckIcon, ChevronsUpDown, Copy, RotateCcw } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { DrawerLogs } from "@/components/shared/drawer-logs";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -34,17 +43,8 @@ import {
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import copy from "copy-to-clipboard";
import { debounce } from "lodash";
import { CheckIcon, ChevronsUpDown, Copy, RotateCcw } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
import { formatBytes } from "../../database/backups/restore-backup";
import { AlertBlock } from "@/components/shared/alert-block";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
interface Props {
id: string;
@@ -1,3 +1,11 @@
import {
ClipboardList,
DatabaseBackup,
Loader2,
Play,
Trash2,
} from "lucide-react";
import { toast } from "sonner";
import { DialogAction } from "@/components/shared/dialog-action";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -15,16 +23,8 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import {
ClipboardList,
DatabaseBackup,
Loader2,
Play,
Trash2,
} from "lucide-react";
import { toast } from "sonner";
import { HandleVolumeBackups } from "./handle-volume-backups";
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
import { HandleVolumeBackups } from "./handle-volume-backups";
import { RestoreVolumeBackups } from "./restore-volume-backups";
interface Props {
@@ -1,3 +1,8 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
@@ -18,11 +23,7 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
interface Props {
composeId: string;
}
@@ -0,0 +1,241 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
} from "@/components/ui/form";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
interface Props {
composeId: string;
}
// Schema for Isolated Deployment
const isolatedSchema = z.object({
isolatedDeployment: z.boolean().optional(),
});
type IsolatedSchema = z.infer<typeof isolatedSchema>;
export const IsolatedDeploymentTab = ({ composeId }: Props) => {
const utils = api.useUtils();
const [compose, setCompose] = useState<string>("");
const [isPreviewLoading, setIsPreviewLoading] = useState<boolean>(false);
const { mutateAsync, error, isError } =
api.compose.isolatedDeployment.useMutation();
const [isOpenPreview, setIsOpenPreview] = useState<boolean>(false);
const { mutateAsync: updateCompose } = api.compose.update.useMutation();
const { data, refetch } = api.compose.one.useQuery(
{ composeId },
{ enabled: !!composeId },
);
const form = useForm<IsolatedSchema>({
defaultValues: {
isolatedDeployment: false,
},
resolver: zodResolver(isolatedSchema),
});
useEffect(() => {
if (data) {
form.reset({
isolatedDeployment: data?.isolatedDeployment || false,
});
}
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
const onSubmit = async (formData: IsolatedSchema) => {
await updateCompose({
composeId,
isolatedDeployment: formData?.isolatedDeployment || false,
})
.then(async (_data) => {
await refetch();
toast.success("Compose updated");
})
.catch(() => {
toast.error("Error updating the compose");
});
};
const generatePreview = async () => {
setIsOpenPreview(true);
setIsPreviewLoading(true);
try {
await mutateAsync({
composeId,
suffix: data?.appName || "",
}).then(async (data) => {
await utils.project.all.invalidate();
setCompose(data);
});
} catch {
toast.error("Error generating preview");
setIsOpenPreview(false);
} finally {
setIsPreviewLoading(false);
}
};
return (
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">Enable Isolated Deployment</CardTitle>
<CardDescription>
Configure isolated deployment to the compose file.
<div className="text-sm text-muted-foreground flex flex-col gap-2">
<span>
This feature creates an isolated environment for your deployment
by adding unique prefixes to all resources. It establishes a
dedicated network based on your compose file's name, ensuring your
services run in isolation. This prevents conflicts when running
multiple instances of the same template or services with identical
names.
</span>
<div className="space-y-4">
<div>
<h4 className="font-medium mb-2">
Resources that will be isolated:
</h4>
<ul className="list-disc list-inside">
<li>Docker networks</li>
</ul>
</div>
</div>
</div>
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="isolated-deployment-form"
className="grid w-full gap-4"
>
{isError && (
<div className="flex flex-row gap-4 rounded-lg items-center bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
<div className="flex flex-col lg:flex-col gap-4 w-full">
<div>
<FormField
control={form.control}
name="isolatedDeployment"
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>
Enable Isolated Deployment ({data?.appName})
</FormLabel>
<FormDescription>
Enable isolated deployment to the compose file.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
<Button
form="isolated-deployment-form"
type="submit"
className="lg:w-fit"
isLoading={form.formState.isSubmitting}
>
Save
</Button>
</div>
</div>
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
<Button
onClick={generatePreview}
isLoading={isPreviewLoading}
variant="secondary"
className="lg:w-fit"
>
Preview Compose
</Button>
<Dialog open={isOpenPreview} onOpenChange={setIsOpenPreview}>
<DialogContent className="sm:max-w-6xl max-h-[80vh]">
<DialogHeader>
<DialogTitle>Isolated Deployment Preview</DialogTitle>
<DialogDescription>
Preview of the compose file with isolated deployment
configuration
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4 overflow-auto">
{isPreviewLoading ? (
<div className="flex flex-col items-center justify-center py-12 gap-4">
<Loader2 className="w-8 h-8 animate-spin text-muted-foreground" />
<p className="text-muted-foreground">
Generating compose preview...
</p>
</div>
) : (
<pre>
<CodeEditor
value={compose || ""}
language="yaml"
readOnly
height="60vh"
/>
</pre>
)}
</div>
</DialogContent>
</Dialog>
</div>
</form>
</Form>
</div>
</CardContent>
</Card>
);
};
@@ -1,3 +1,13 @@
import type { ServiceType } from "@dokploy/server/db/schema";
import { zodResolver } from "@hookform/resolvers/zod";
import copy from "copy-to-clipboard";
import { Copy, Trash2 } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
@@ -20,15 +30,6 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import type { ServiceType } from "@dokploy/server/db/schema";
import { zodResolver } from "@hookform/resolvers/zod";
import copy from "copy-to-clipboard";
import { Copy, Trash2 } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const deleteComposeSchema = z.object({
projectName: z.string().min(1, {
@@ -100,7 +101,9 @@ export const DeleteService = ({ id, type }: Props) => {
deleteVolumes,
})
.then((result) => {
push(`/dashboard/project/${result?.projectId}`);
push(
`/dashboard/project/${result?.environment?.projectId}/environment/${result?.environment?.environmentId}`,
);
toast.success("deleted successfully");
setIsOpen(false);
})
@@ -114,6 +117,12 @@ export const DeleteService = ({ id, type }: Props) => {
}
};
const isDisabled =
(data &&
"applicationStatus" in data &&
data?.applicationStatus === "running") ||
(data && "composeStatus" in data && data?.composeStatus === "running");
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
@@ -202,6 +211,12 @@ export const DeleteService = ({ id, type }: Props) => {
</form>
</Form>
</div>
{isDisabled && (
<AlertBlock type="warning" className="w-full mt-5">
Cannot delete the service while it is running. Please wait for the
build to finish and then try again.
</AlertBlock>
)}
<DialogFooter>
<Button
variant="secondary"
@@ -211,8 +226,10 @@ export const DeleteService = ({ id, type }: Props) => {
>
Cancel
</Button>
<Button
isLoading={isLoading}
disabled={isDisabled}
form="hook-form-delete-compose"
type="submit"
variant="destructive"
@@ -1,3 +1,7 @@
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
@@ -8,10 +12,6 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
interface Props {
@@ -47,7 +47,7 @@ export const ComposeActions = ({ composeId }: Props) => {
toast.success("Compose deployed successfully");
refetch();
router.push(
`/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`,
`/dashboard/project/${data?.environment.projectId}/environment/${data?.environmentId}/services/compose/${composeId}?tab=deployments`,
);
})
.catch(() => {
@@ -195,6 +195,7 @@ export const ComposeActions = ({ composeId }: Props) => {
<DockerTerminalModal
appName={data?.appName || ""}
serverId={data?.serverId || ""}
appType={data?.composeType || "docker-compose"}
>
<Button
variant="outline"
@@ -1,3 +1,8 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
@@ -8,13 +13,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
import { ShowUtilities } from "./show-utilities";
interface Props {
composeId: string;
@@ -36,6 +35,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
);
const { mutateAsync, isLoading } = api.compose.update.useMutation();
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const form = useForm<AddComposeFile>({
defaultValues: {
@@ -54,6 +54,12 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
}
}, [form, form.reset, data]);
useEffect(() => {
if (data?.composeFile !== undefined) {
setHasUnsavedChanges(composeFile !== data.composeFile);
}
}, [composeFile, data?.composeFile]);
const onSubmit = async (data: AddComposeFile) => {
const { valid, error } = validateAndFormatYAML(data.composeFile);
if (!valid) {
@@ -72,6 +78,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
})
.then(async () => {
toast.success("Compose config Updated");
setHasUnsavedChanges(false);
refetch();
await utils.compose.getConvertedCompose.invalidate({
composeId,
@@ -100,6 +107,19 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
return (
<>
<div className="w-full flex flex-col gap-4 ">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-medium">Compose File</h3>
<p className="text-sm text-muted-foreground">
Configure your Docker Compose file for this service.
{hasUnsavedChanges && (
<span className="text-yellow-500 ml-2">
(You have unsaved changes)
</span>
)}
</p>
</div>
</div>
<Form {...form}>
<form
id="hook-form-save-compose-file"
@@ -142,9 +162,7 @@ services:
</form>
</Form>
<div className="flex justify-between flex-col lg:flex-row gap-2">
<div className="w-full flex flex-col lg:flex-row gap-4 items-end">
<ShowUtilities composeId={composeId} />
</div>
<div className="w-full flex flex-col lg:flex-row gap-4 items-end" />
<Button
type="submit"
form="hook-form-save-compose-file"
@@ -1,3 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
@@ -40,13 +47,6 @@ import {
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const BitbucketProviderSchema = z.object({
composePath: z.string().min(1),
@@ -1,3 +1,11 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { GitIcon } from "@/components/icons/data-tools-icons";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -27,14 +35,6 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const GitProviderSchema = z.object({
composePath: z.string().min(1),

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