Compare commits

...

402 Commits

Author SHA1 Message Date
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
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]
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
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
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
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
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
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
f8261b5364 feat(dashboard): use username instead of email for the generation of the
fallback avatar image
2025-07-28 19:20:05 +02:00
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
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
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
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
Mauricio Siu
3f3ff9670b chore(package): bump version to v0.24.2 2025-07-13 20:45:33 -06:00
Mauricio Siu
7fb902551e Merge pull request #2189 from jhon2c/fix/logs-overflow
fix(logs): Restore overflow classnames in logs components
2025-07-13 20:44:34 -06:00
Jhon
a201b3f979 fix(ui): regression of overflow-y-auto class in non dialog related componentes 2025-07-13 21:28:50 -03:00
Jhon
01d78e50fc fix(logs): adds back overflow classnames 2025-07-13 21:09:12 -03:00
Mauricio Siu
6681ba7bbd Merge pull request #2185 from Dokploy/fix/make-monitoring-restart-automatically
feat(monitoring): add RestartPolicy configuration for server and web …
2025-07-13 13:18:09 -06:00
Mauricio Siu
0b71411c0e feat(monitoring): add RestartPolicy configuration for server and web monitoring setups 2025-07-13 13:17:48 -06:00
Mauricio Siu
19f7465910 chore(docker): activate pnpm 9.12.0 in all Dockerfiles 2025-07-13 13:11:19 -06:00
Mauricio Siu
f33dd37571 Merge pull request #2184 from Dokploy/refactor/update-docker-base-images
chore(docker): update Node.js version to 20.16.0 in all Dockerfiles
2025-07-13 12:54:32 -06:00
Mauricio Siu
a0031ed07f chore(docker): update Node.js version to 20.16.0 in all Dockerfiles 2025-07-13 12:03:01 -06:00
Mauricio Siu
2ca4e264c4 Merge pull request #2082 from Marukome0743/dependencies
chore: match dependencies with current ones in pnpm-lock.yaml
2025-07-13 12:01:12 -06:00
Mauricio Siu
fa81d04fb3 Merge pull request #2164 from croatialu/fix/gitlab-deployments
fix(gitlab): Support dynamically generating clone URLs based on protocols
2025-07-13 11:55:19 -06:00
Mauricio Siu
bd8745393b chore(package): bump version to v0.24.1 2025-07-13 11:55:04 -06:00
autofix-ci[bot]
691c83c256 [autofix.ci] apply automated fixes 2025-07-13 17:54:36 +00:00
Mauricio Siu
6bd85e9216 Merge pull request #2182 from jhon2c/fix/dialog-crash
fix(ui):  Fix Dialogs Infinite Render Loops and Command Component Conflicts
2025-07-13 11:53:11 -06:00
Jhon
79c29fa92d fix(typo): fixed typo on replace classname 2025-07-13 13:58:25 -03:00
autofix-ci[bot]
89f71fe889 [autofix.ci] apply automated fixes 2025-07-13 16:50:41 +00:00
Jhon
bddafe294d fix(classname): removes leading blank space on classnames 2025-07-13 13:47:27 -03:00
Jhon
94829daf15 fix(ui): code formatting and DialogHeader improvements
- Apply consistent code formatting across dialog components
- Add bottom padding to DialogHeader for better visual separation
- Clean up DialogHeader usage in swarm settings (remove duplicate padding)
- Improve schedule dialog layout and add proper description
- Fix indentation and formatting inconsistencies

Final cleanup of dialog component formatting and spacing.
2025-07-13 13:35:26 -03:00
Jhon
2209d44ea5 fix(ui): update remaining dialog components with improved layouts
- Fix application import dialog positioning
- Update organization management dialog styling
- Ensure consistent DialogFooter behavior across all components

Completes the dialog layout improvements for better spacing and positioning.
2025-07-13 13:03:24 -03:00
Jhon
b12c035527 fix(ui): improve DialogFooter layout in settings dialogs
- Update certificate management dialog footer styling
- Enhance destination settings dialog layout
- Improve notification settings dialog footer spacing
- Add responsive design improvements for server creation dialog

Ensures consistent footer behavior across settings panels.
2025-07-13 13:03:11 -03:00
Jhon
baadba542f fix(ui): update DialogFooter styling in cluster management dialogs
- Add responsive layout and proper spacing to swarm settings footer
- Update registry dialog footer with improved flex layout
- Ensure proper button alignment on mobile and desktop
- Add sticky positioning for better UX in long forms
2025-07-13 13:01:36 -03:00
Jhon
a8fc052cbf fix(ui): resolve dialog closing issues with Command components
- Replace custom overlay click handler with proper onInteractOutside
- Add detection for Command components to prevent unwanted closures
- Restore overlay visibility without click handler conflicts
- Separate DialogFooter from scrollable content for proper spacing
- Add border and padding to DialogFooter container for visual separation

Fixes dialogs closing unexpectedly when used inside Command menus.
2025-07-13 13:00:21 -03:00
Jhon
fa5994bd47 fix(ui): remove max-h-screen and overflow-y-auto from remaining dialogs
Clean up any remaining dialog components with problematic CSS classes.
Complete removal of classes that interfere with new scroll handling system.
2025-07-13 12:17:05 -03:00
Jhon
96d0810607 fix(ui): remove max-h-screen and overflow-y-auto from project and database dialogs
Remove problematic CSS classes from:
- Project creation and management dialogs
- Database backup and restore dialogs
- Compose service management dialogs
- Template and AI generator dialogs

Ensures stable dialog behavior.
2025-07-13 12:16:51 -03:00
Jhon
2d382ea1be fix(ui): remove max-h-screen and overflow-y-auto from settings dialogs
Remove problematic CSS classes from system settings:
- Git provider configurations
- User management dialogs
- API key management
- Certificate management
- Notification settings
- Server management dialogs
- Profile and 2FA settings

Fixes render loops in admin panels.
2025-07-13 12:16:35 -03:00
Jhon
d78974efc0 fix(ui): remove max-h-screen and overflow-y-auto from advanced settings dialogs
Remove problematic CSS classes from advanced application dialogs:
- Cluster and swarm settings
- Port configuration
- Security settings
- Traefik configuration
- Volume management
- Redirect configuration

Prevents tab hangs with overflow content.
2025-07-13 12:15:36 -03:00
Jhon
81040c899f fix(ui): remove max-h-screen and overflow-y-auto from application feature dialogs
Remove problematic CSS classes from:
- Domain management dialogs
- Preview deployment dialogs
- Schedule configuration dialogs
- Volume backup dialogs

Ensures proper scrolling without render loops.
2025-07-13 12:15:09 -03:00
Jhon
c7344190b4 fix(ui): remove max-h-screen and overflow-y-auto from deployment dialogs
Remove problematic CSS classes from:
- Application deployment modals
- Docker logs modals
- Swarm application dialogs

Fixes infinite render loops with tall content.
2025-07-13 12:14:49 -03:00
Jhon
257c0eb106 fix(ui): remove max-h-screen and overflow-y-auto from service update dialogs
Remove problematic CSS classes that cause infinite render loops in:
- Application update dialog
- Database update dialogs (Redis, MariaDB, MongoDB, PostgreSQL, MySQL)
- Compose update dialog

These classes are now handled internally by the DialogContent component.
2025-07-13 12:14:36 -03:00
Jhon
c03b9509c8 fix(ui): resolve dialog infinite render loops with tall content
- Force modal=false on all dialogs to prevent Radix UI render loops
- Add React context to share dialog state between components
- Implement custom overlay with proper click-to-close behavior
- Add body scroll lock tied to dialog open state (prevents stuck scroll)
- Create scrollable content wrapper with overscroll-contain
- Remove complex wheel event handlers that caused tab hangs
- Simplify dialog architecture for better maintainability
2025-07-13 11:36:10 -03:00
Mauricio Siu
d87205c4dc chore: update README.md by removing outdated sponsor links and adjusting community backers section 2025-07-13 01:56:17 -06:00
Mauricio Siu
48aef798e4 Merge pull request #2176 from gentslava/fix/git-providers-layout
fix(ui): git providers overflow
2025-07-13 01:23:48 -06:00
Vyacheslav Scherbinin
baa5cd5c58 fix(ui): available git providers layout 2025-07-12 14:05:44 +07:00
Vyacheslav Scherbinin
5aae36996e fix(ui): buttons grow 2025-07-12 13:52:53 +07:00
Vyacheslav Scherbinin
ec8fa9fefe fix(ui): buttons wrap 2025-07-12 13:50:43 +07:00
Vyacheslav Scherbinin
d959f59c2d fix(typo): double space 2025-07-12 13:36:28 +07:00
Mauricio Siu
a1169795e4 Merge pull request #2163 from croatialu/fix/gitlab-url
fix: Add gitlabUrl calculation logic and update link references
2025-07-12 00:15:12 -06:00
Mauricio Siu
10af7925db Merge pull request #2156 from gentslava/fix/overflow-scroll
fix(ui): tabs overflow and Tailwind config
2025-07-11 22:58:41 -06:00
Mauricio Siu
c64cdca2e8 Merge pull request #2174 from Dokploy/2147-container-name-exceeds-63-characters-when-cloning-multiple-projects
feat(project): update application name handling during duplication
2025-07-11 22:53:06 -06:00
Mauricio Siu
a5b95d8cf3 feat(project): update application name handling during duplication
- Extracted and modified the application name by removing the suffix after the last hyphen when duplicating various application types (Postgres, MariaDB, Mongo, MySQL, Redis, Compose).
- Ensured consistent naming for duplicated applications across different database types.
2025-07-11 22:52:47 -06:00
Mauricio Siu
78b60f7d8a Merge pull request #2167 from croatialu/fix/traefik-config-editor-mask
fix: Optimize the code editor component, adjust the style and structu…
2025-07-11 22:29:23 -06:00
autofix-ci[bot]
58e6a14cd6 [autofix.ci] apply automated fixes 2025-07-12 04:28:55 +00:00
croatialu
0aac6da554 fix: Optimize the code editor component, adjust the style and structure to ensure the overlay is correctly rendered in the disabled state. 2025-07-11 14:19:58 +08:00
croatialu
978c4d85c5 fix(gitlab): Support dynamically generating clone URLs based on protocols 2025-07-11 13:33:46 +08:00
croatialu
70e08c96eb fix: Add gitlabUrl calculation logic and update link references
- Use the useMemo hook to calculate gitlabUrl in the SaveGitlabProvider component.
- Update link references to use the dynamically generated gitlabUrl, ensuring links correctly point to the corresponding GitLab repositories.
2025-07-11 11:55:16 +08:00
Vyacheslav Scherbinin
027853a361 fix(ui): change gap 2025-07-09 18:20:07 +07:00
Vyacheslav Scherbinin
43ebe4dc7c fix(config): the min- and max- variants are not supported with a screens configuration containing mixed units 2025-07-09 18:17:32 +07:00
Vyacheslav Scherbinin
0113ebe7da fix(ui): compose provider tabs layout 2025-07-09 14:20:17 +07:00
Vyacheslav Scherbinin
c36b40aa29 fix(ui): application provider tabs layout 2025-07-09 14:20:07 +07:00
Vyacheslav Scherbinin
caea934f88 fix(typo): double space 2025-07-09 14:16:02 +07:00
autofix-ci[bot]
9b2ea1cade [autofix.ci] apply automated fixes 2025-07-09 07:07:04 +00:00
Vyacheslav Scherbinin
3a82c4b27b fix(ui): compose tabs overflow 2025-07-09 13:53:14 +07:00
Vyacheslav Scherbinin
22a26e9873 fix(ui): application tabs overflow 2025-07-09 13:53:05 +07:00
Marukome0743
226a287ce7 chore: update package.json 2025-07-09 15:33:37 +09:00
Mauricio Siu
320b927aac Merge pull request #2152 from nktnet1/fix-ui-compose-tablist
fix(ui): adjust tablist item width for compose services
2025-07-08 21:47:16 -06:00
Khiet Tam Nguyen
d799b460bd fix(ui): adjust tablist item width for compose services 2025-07-08 20:08:26 +10:00
Mauricio Siu
3ed9da147b Merge pull request #2144 from Marukome0743/sponser
docs: fix broken README.md sponsers images
2025-07-06 21:16:45 -06:00
Marukome0743
636dec4f09 docs: fix broken README.md sponsers images 2025-07-07 10:35:32 +09:00
Mauricio Siu
4dcf6cf4c3 refactor(volume-backups): comment out keepLatestCount field and related logic
- Commented out the keepLatestCount field in the form schema and its usage in the HandleVolumeBackups component.
- Updated related form field rendering to prevent rendering of the keepLatestCount input.
2025-07-06 18:58:26 -06:00
Mauricio Siu
7356d71626 Merge pull request #1866 from Dokploy/1853-external-volume-should-not-be-isolated
refactor: remove unused volume suffix function from collision utility
2025-07-06 18:50:47 -06:00
Mauricio Siu
76603f598c feat(database): add isolatedDeploymentsVolume column to compose table
- Introduced a new boolean column "isolatedDeploymentsVolume" to the "compose" table with a default value of false.
- Updated existing records to set "isolatedDeploymentsVolume" to true.
- Modified related functions to handle the new column for improved deployment isolation management.
2025-07-06 18:46:36 -06:00
Mauricio Siu
e050c218e2 Merge branch 'canary' into 1853-external-volume-should-not-be-isolated 2025-07-06 18:29:00 -06:00
Mauricio Siu
46e0b5df75 Update package.json 2025-07-06 17:57:22 -06:00
Mauricio Siu
5b36503a3f Merge pull request #2134 from andr3i1010/canary
fix: fix environment variable parsing for railpack
2025-07-06 17:09:04 -06:00
Mauricio Siu
b9afc551db Merge pull request #2142 from Dokploy/fix/rollbacks-dns-issue
fix(rollbacks): correct dns issue resolution
2025-07-06 16:44:14 -06:00
Mauricio Siu
078ca19578 feat(rollbacks): enhance rollback functionality with full context support
- Updated the rollbacks schema to include mounts, ports, and optional registry information in the full context.
- Refactored the rollback service to utilize the full context for improved rollback operations, ensuring all necessary configurations are applied.
- Exported the getAuthConfig function for better accessibility in the application context.
2025-07-06 16:43:44 -06:00
Mauricio Siu
b7dc7bbf0c Merge pull request #2141 from Dokploy/1992-gitlab-repository-selection
fix(gitlab): update repository selection to use URL instead of name
2025-07-06 13:57:13 -06:00
Mauricio Siu
b9ee81aa59 fix(gitlab): update repository selection to use URL instead of name
- Changed the repository selection logic in SaveGitlabProvider and SaveGitlabProviderCompose components to use the repository URL for better accuracy.
- Updated the condition for displaying the selected repository in the CheckIcon component to compare against the gitlabPathNamespace instead of the repository name.
- Refactored the getGitlabRepositories function to simplify the group name check logic.
2025-07-06 13:56:58 -06:00
Mauricio Siu
b2d01a2889 Merge pull request #2140 from Dokploy/1187-swarm-service-bind-mounts-not-working-unless-manually-created
feat(volumes): add alert block for bind mount validation in AddVolume…
2025-07-05 17:18:24 -06:00
Mauricio Siu
5ec3d63ab2 feat(volumes): add alert block for bind mount validation in AddVolumes component
- Introduced an AlertBlock to provide users with warnings about host path validity and potential deployment issues when using bind mounts in the AddVolumes component.
- This enhancement improves user experience by ensuring users are informed of important considerations when configuring volumes.
2025-07-05 17:17:51 -06:00
Mauricio Siu
f0ef06ed8c Merge pull request #2139 from Dokploy/1541-register-login-screen-fails-on-chrome
1541 register login screen fails on chrome
2025-07-05 17:07:51 -06:00
Mauricio Siu
75b2c34a13 feat(api): implement lazy WebSocket client for improved connection management
- Introduced a `createLazyWSClient` function to manage WebSocket connections more efficiently by delaying the creation of the client until it's needed.
- Updated the `wsClient` initialization to use the new lazy client, enhancing performance and resource management in the application.
2025-07-05 17:06:39 -06:00
Mauricio Siu
cd4533df9e Merge branch 'canary' into 1541-register-login-screen-fails-on-chrome 2025-07-05 17:03:01 -06:00
Mauricio Siu
65a3d8175a Merge pull request #2138 from Dokploy/2109-reload-results-in-temporary-downtime-despite-healthcheck-zero-downtime-configured
refactor(application): remove redundant service stop logic
2025-07-05 16:48:18 -06:00
Mauricio Siu
d3702d22f2 refactor(application): remove redundant service stop logic
- Eliminated the conditional logic for stopping services based on serverId, streamlining the application status update process.
- This change enhances code clarity and maintains focus on application state management.
2025-07-05 16:47:44 -06:00
Mauricio Siu
d4b74c54da Merge pull request #2116 from Dokploy/394-ability-to-backup-named-volume-to-s3
394 ability to backup named volume to s3
2025-07-05 16:05:49 -06:00
Mauricio Siu
80ede659fb feat(volume-backups): enhance volume backup display with restore functionality
- Wrapped the volume backup handling component with a fragment to include the restore functionality.
- Added a new div for better layout and organization of the volume backup and restore components, improving user experience.
2025-07-05 16:00:01 -06:00
Mauricio Siu
c59ea57814 feat(database): add volume_backup table and update deployment schema
- Introduced a new "volume_backup" table to manage volume backup configurations, including fields for various database IDs and backup settings.
- Updated the "deployment" table to include a foreign key reference to the new "volume_backup" table.
- Added foreign key constraints to ensure data integrity across related tables.
- Updated journal and snapshot files to reflect these changes, enhancing the database schema for better backup management.
2025-07-05 15:40:57 -06:00
Mauricio Siu
381186c9f1 Merge branch 'canary' into 394-ability-to-backup-named-volume-to-s3 2025-07-05 15:40:25 -06:00
Mauricio Siu
05e0031daf refactor(database): remove volume_backup table and associated metadata
- Deleted the "volume_backup" table and its related SQL definitions, including foreign key constraints in the "deployment" table.
- Removed corresponding entries from the journal and snapshot files to ensure consistency and maintain a clean database schema.
- This cleanup improves the organization and maintainability of the database structure.
2025-07-05 15:40:14 -06:00
Mauricio Siu
1bacd42bf5 Merge pull request #2053 from jhon2c/feat/internal-path-routing
feat: add internal path routing and path stripping for domains
2025-07-05 15:34:58 -06:00
Mauricio Siu
2bc12a20ba Merge pull request #2128 from DearTanker/canary
Style: Change Node Applications dialog width, more easy to read.
2025-07-05 14:44:45 -06:00
Mauricio Siu
1943a9e8fa Merge pull request #2137 from Dokploy/2055-bug-report-requests-tab-limited-to-500-entries
fix(settings): update readMonitoringConfig to async and improve log r…
2025-07-05 14:44:34 -06:00
Mauricio Siu
db9109a3be fix(settings): update readMonitoringConfig to async and improve log reading efficiency 2025-07-05 14:43:49 -06:00
Jhonatan Caldeira
2f6084ec8f fix(conflicts): removed conflicted migrations 2025-07-05 16:08:09 -03:00
Jhonatan Caldeira
a62c9f63e1 Merge branch 'canary' into feat/internal-path-routing 2025-07-05 15:50:04 -03:00
autofix-ci[bot]
ee2bbf5e37 [autofix.ci] apply automated fixes 2025-07-05 17:38:16 +00:00
andrei1010
d27dff4906 fix: fix environment variable parsing for railpack 2025-07-05 17:14:28 +02:00
DearTanker
7874445510 Style: Change Node Applications dialog width, more easy to read. 2025-07-05 17:24:11 +08:00
Mauricio Siu
5c73ced500 chore(license): update copyright year to 2025 and remove obsolete dokploy license file 2025-07-05 01:38:37 -06:00
Mauricio Siu
b1450d14ac chore(package): bump version to v0.23.7 2025-07-05 01:36:13 -06:00
Mauricio Siu
c5e3b00990 Merge pull request #2125 from Dokploy/fix/issues
Fix/issues
2025-07-05 01:35:22 -06:00
Mauricio Siu
cf3f44f686 feat(database): add volume_backup table and related foreign key constraints
- Introduced a new table "volume_backup" to manage volume backup configurations.
- Added foreign key constraints linking "volumeBackupId" in the "deployment" table to the new "volume_backup" table.
- Updated the journal and snapshot files to reflect these changes, ensuring proper versioning and tracking.
2025-07-05 00:06:07 -06:00
Mauricio Siu
119883e746 Merge branch 'canary' into 394-ability-to-backup-named-volume-to-s3 2025-07-05 00:05:47 -06:00
Mauricio Siu
2918868166 refactor: remove deprecated volume backup SQL files and snapshots
- Deleted SQL files related to the volume_backup table, including type definitions, constraints, and columns that are no longer in use.
- Removed associated snapshot files to maintain consistency and reduce clutter in the database schema.
- This cleanup enhances the overall organization and maintainability of the database structure.
2025-07-05 00:05:36 -06:00
Mauricio Siu
715e44116d Merge pull request #2030 from s1nyx/fix/traefik-setup-was-not-pulling-image
fix(traefik-setup): now pulling the traefik image to make it sure it's present locally
2025-07-05 00:03:50 -06:00
Mauricio Siu
c15ee721ff feat(setup): add async execution of docker pull for traefik image during setup 2025-07-05 00:01:41 -06:00
Mauricio Siu
71befc3a4b Merge pull request #2115 from zuohuadong/canary
feat: fix OpencloudOS setup
2025-07-04 23:50:24 -06:00
Mauricio Siu
bca32f077b Merge pull request #2084 from Marukome0743/ubuntu
docs: update docker installation method of Ubuntu
2025-07-04 23:45:44 -06:00
Mauricio Siu
1afcecaec0 Merge pull request #2124 from Dokploy/feat/add-ports-publish-mode
Feat/add ports publish mode
2025-07-04 23:44:18 -06:00
Mauricio Siu
80b72dc75e fix: handle potential null values for publishMode in ShowPorts component 2025-07-04 23:42:35 -06:00
Mauricio Siu
5973d7b9b8 Merge branch 'canary' into feat/add-ports-publish-mode 2025-07-04 23:38:37 -06:00
autofix-ci[bot]
ab3a1504cf [autofix.ci] apply automated fixes 2025-07-05 05:37:11 +00:00
Mauricio Siu
f51c51958f feat: add publishMode column to port schema
- Introduced a new ENUM type "publishModeType" with values 'ingress' and 'host'.
- Added "publishMode" column to the "port" table with a default value of 'host'.
- Updated related metadata files to reflect the changes.
2025-07-04 23:33:21 -06:00
Mauricio Siu
f3703e6f5e Merge pull request #2112 from Marukome0743/error
refactor: remove unused catch errors
2025-07-04 23:29:28 -06:00
Mauricio Siu
14cb6cecae Merge pull request #2107 from DearTanker/canary
Style: Make all code editors apply Tailwind CSS font-mono style.
2025-07-04 23:24:01 -06:00
Mauricio Siu
6885b140eb Merge pull request #2108 from Marukome0743/blue
docs: remove blue underlink from README.md
2025-07-04 23:22:26 -06:00
Mauricio Siu
8e8712e33d style: align text in file tree component for improved readability 2025-07-04 23:17:19 -06:00
FelipeVasquez350
56fcaa8ccd Update show-port.tsx 2025-07-04 18:15:43 +02:00
FelipeVasquez350
d229284e5e Update handle-ports.tsx 2025-07-04 18:08:23 +02:00
FelipeVasquez350
3561b5cae6 feat: add option for publishMode in an application port settings 2025-07-04 18:01:13 +02:00
DearTanker
a3192d6584 Merge branch 'Dokploy:canary' into canary 2025-07-03 10:48:52 +08:00
DearTanker
24ea8b7fbd Style: Unspecify the popup list width to make the name appear in full 2025-07-03 10:39:58 +08:00
Marukome0743
e12df7b32e refactor: remove unused catch errors 2025-07-03 08:42:04 +09:00
Marukome0743
8001c9cfc2 docs: remove blue underlink from README.md 2025-07-03 08:41:38 +09:00
Marukome0743
a762b4b4ae docs: update docker installation method of Ubuntu 2025-07-03 08:41:08 +09:00
Jhonatan Caldeira
9cfbd664c5 Merge branch 'Dokploy:canary' into feat/internal-path-routing 2025-07-02 10:54:18 -03:00
huadong zuo
f9972bee60 Merge branch 'Dokploy:canary' into canary 2025-07-02 14:52:42 +08:00
Mauricio Siu
3970cd452b refactor: update volume backup imports for improved organization
- Changed import paths for findVolumeBackupById and findComposeById to enhance clarity and maintainability.
- Updated import structure to align with the latest service organization in the @dokploy/server package.
2025-07-02 00:52:25 -06:00
zuohuadong
6fc51e02a7 Revert "feat(swap): On hosts with less than 2G of memory, increase swap space to prevent installation failure."
This reverts commit 207fe6f477.
2025-07-02 14:51:48 +08:00
zuohuadong
fa7db0dc75 Revert "chore(setup):Increase interactive options"
This reverts commit 0c861585ed.
2025-07-02 14:50:39 +08:00
Mauricio Siu
107cdcee49 feat: extend volume backup functionality with scheduling and management
- Added support for volume backup jobs in the scheduling and removal processes.
- Enhanced job management to include volume backups in the job queue.
- Updated schema to accommodate volume backup data structure.
- Implemented initialization of volume backup jobs based on server status, improving backup management.
2025-07-02 00:45:57 -06:00
Mauricio Siu
6521491e2f feat: enhance volume backup scheduling and management
- Added initVolumeBackupsCronJobs function to initialize scheduled volume backups on server startup.
- Updated volumeBackupsRouter to handle scheduling and removal of volume backup jobs based on user input.
- Improved create and update volume backup logic to include scheduling functionality for both cloud and local environments.
- Introduced utility functions for scheduling and removing volume backup jobs, enhancing overall backup management.
2025-07-02 00:36:46 -06:00
Mauricio Siu
c5311f2a9f feat: implement volume backup and restore functionalities
- Added backupVolume and restoreVolume functions to handle the backup and restoration of volume data.
- Introduced a new index file to streamline exports for volume backup utilities.
- Updated volume backup logic to include scheduling and improved user feedback during operations.
- Refactored existing volume backup utilities for better organization and clarity.
2025-07-02 00:23:27 -06:00
Mauricio Siu
7726c8db21 fix: refine volume backup logic and enhance user feedback
- Updated HandleVolumeBackups to conditionally enable mount retrieval based on volumeBackupType, improving accuracy in fetching mounts.
- Enhanced backupVolume function to provide user feedback upon starting the compose container, ensuring better clarity during operations.
2025-07-02 00:09:07 -06:00
Mauricio Siu
b272f01a18 fix: update volume backup components for improved functionality
- Refactored HandleVolumeBackups to use consistent naming for mount items.
- Added an AlertBlock to RestoreVolumeBackups to inform users about potential volume name conflicts.
- Updated mount retrieval logic in the mount router to enhance accuracy and efficiency in fetching volume mounts.
2025-07-01 23:48:19 -06:00
Mauricio Siu
bddb07898e Merge branch 'canary' into 394-ability-to-backup-named-volume-to-s3 2025-07-01 23:32:50 -06:00
Mauricio Siu
2fe7349889 chore: update version in package.json to v0.23.6 2025-07-01 23:18:44 -06:00
Mauricio Siu
5c1c969873 Merge pull request #2113 from Dokploy/fix/schedules-rm
fix: prevent removal of current directory in deployment logs
2025-07-01 23:18:15 -06:00
Mauricio Siu
2f6f1b19e7 fix: prevent removal of current directory in deployment logs
- Updated the removeLastTenDeployments function to check if logPath is not the current directory before executing the removal command.
- Enhanced the unlink operation to ensure it only attempts to delete valid log paths.
2025-07-01 23:17:27 -06:00
Mauricio Siu
934ec9b16a fix: improve error handling during volume restoration process
- Simplified error handling in the volume restoration logic by removing detailed error messages and stack traces.
- Updated user feedback to provide clearer instructions when a volume is in use, enhancing the user experience during restoration attempts.
- Ensured that the restoration process aborts gracefully with informative messages when prerequisites are not met.
2025-07-01 01:21:54 -06:00
Mauricio Siu
c6d760a904 fix: update RestoreVolumeBackups and ShowVolumeBackups components for improved functionality
- Refactored the RestoreVolumeBackups component to ensure the type prop is required and added serverId handling for better integration.
- Corrected variable naming for destinationId in the form handling to prevent potential issues.
- Enhanced the ShowVolumeBackups component to pass serverId to the RestoreVolumeBackups component, ensuring consistent data flow.
- Improved user interface elements for backup file selection, ensuring better usability and clarity.
2025-07-01 01:12:20 -06:00
Mauricio Siu
4f021a3f79 feat: add restore volume backups component and integrate into show volume backups
- Introduced a new RestoreVolumeBackups component to facilitate the restoration of volume backups from selected files and destinations.
- Integrated the RestoreVolumeBackups component into the ShowVolumeBackups component, enhancing user experience by providing direct access to restoration functionality.
- Updated the restore-backup schema to include validation for destination and backup file selection, ensuring robust user input handling.
2025-06-30 22:50:46 -06:00
zuohuadong
0c861585ed chore(setup):Increase interactive options 2025-07-01 12:19:39 +08:00
Mauricio Siu
d15ccfe505 feat: add runVolumeBackup functionality and update volume backup paths
- Implemented the runVolumeBackup function to facilitate manual execution of volume backups, enhancing user control over backup processes.
- Updated various components to utilize the new VOLUME_BACKUPS_PATH for improved organization and clarity in backup file management.
- Enhanced error handling in the runManually mutation to ensure robust execution and logging of backup operations.
2025-06-30 22:15:45 -06:00
huadong zuo
e158e05ad6 Merge branch 'Dokploy:canary' into canary 2025-07-01 09:40:36 +08:00
Mauricio Siu
e21605030a fix: adjust scaling command in backupVolume function
- Updated the backupVolume function to use the actual number of replicas when scaling services back up after a backup, improving accuracy in service management.
- Enhanced command generation for application service types to ensure proper restoration of service states.
2025-06-30 11:27:18 -06:00
Jhonatan Caldeira
f1c46f0d19 fix(conflicts): redo database migrations broken due to upstream branch conflict 2025-06-30 11:26:33 -03:00
Jhonatan Caldeira
fbf3776548 Merge branch 'canary' into feat/internal-path-routing 2025-06-30 10:56:19 -03:00
Mauricio Siu
46d84eaa71 feat: implement volume restoration functionality
- Added a new restoreVolume function to facilitate the restoration of Docker volumes from backups.
- Integrated service type handling for both application and compose, ensuring proper scaling and management of services during restoration.
- Enhanced command generation for restoring volumes, improving clarity and maintainability of the logic.
2025-06-30 01:38:39 -06:00
Mauricio Siu
2a2b947998 refactor: remove redundant ID assignment in backupVolume command
- Eliminated duplicate ID assignment in the backupVolume function to streamline Docker command generation.
- Improved clarity and maintainability of the command logic by reducing unnecessary lines of code.
2025-06-30 01:35:32 -06:00
Mauricio Siu
9974b2326f refactor: streamline volume backup command generation
- Consolidated Docker command construction for volume backups into a base command to reduce redundancy.
- Enhanced service type handling for application and compose, ensuring proper scaling and stopping of services during backup.
- Improved readability and maintainability of the backup command logic by using template literals and consistent formatting.
2025-06-30 01:33:12 -06:00
Mauricio Siu
c042c8c0c5 feat: implement service-based volume selection for backups
- Added a new query to load mounts by service name, enhancing the volume backup form's functionality.
- Updated the form to allow users to select a service and corresponding volume from a dropdown, improving user experience.
- Retained the option for manual input of volume names, ensuring flexibility in volume selection.
- Refactored the component to streamline the handling of service and volume selections.
2025-06-30 01:25:50 -06:00
Mauricio Siu
819a310d48 feat: add volume selection functionality to volume backup form
- Introduced a new query to fetch named mounts by application ID, enhancing the volume backup form.
- Updated the form to allow users to select a volume from a dropdown, improving user experience.
- Retained the option for manual input of volume names, ensuring flexibility in volume selection.
2025-06-29 23:24:44 -06:00
Mauricio Siu
12860a0736 feat: add appName field to volume_backup schema and enhance deployment volume backup functionality
- Introduced a new appName column in the volume_backup table to improve application identification.
- Updated the volume backup schema to ensure appName is a required field.
- Enhanced the deployment service to support volume backup creation, integrating appName into the deployment process.
- Added validation for volume backup creation to ensure proper handling of appName and related data.
2025-06-29 23:18:12 -06:00
Mauricio Siu
392e2d66ec fix: improve volume backup validation and refactor component props
- Added validation to ensure service name is required when service type is "compose" in the volume backup form schema.
- Refactored the ShowVolumeBackups component to use a more consistent prop name for volume backup type, enhancing clarity and maintainability.
- Updated related components to align with the new prop structure, ensuring seamless integration across the application.
2025-06-29 23:14:53 -06:00
autofix-ci[bot]
1f4ce2daf3 [autofix.ci] apply automated fixes 2025-06-30 05:13:57 +00:00
Mauricio Siu
49edf17463 feat: enhance volume backup functionality and schema
- Added support for volume backups in the deployment management interface by introducing a new volumeBackupId field in the deployment schema.
- Updated the volume backup schema to include relationships with deployments, allowing for better management and tracking of volume backups.
- Enhanced API routes to include application data when querying volume backups, improving the data returned for related entities.
- Updated UI components to reflect the new volume backup features and ensure seamless integration with existing functionalities.
2025-06-29 23:10:49 -06:00
Mauricio Siu
6eea02c098 fix: update default enabled state for volume backups
- Changed the default value of the enabled property in the volume backup handling component from true to false to ensure backups are disabled by default unless explicitly enabled.
2025-06-29 23:07:18 -06:00
Mauricio Siu
a5bba9a11b refactor: update volume backup schema and form handling
- Modified the volume backup schema to enforce non-null constraints on volumeName and added serviceName.
- Removed unnecessary fields (type, hostPath) from the schema and updated related API and form handling.
- Enhanced form validation to ensure required fields are properly checked.
- Updated the UI components to reflect changes in the volume backup management interface.
2025-06-29 23:05:36 -06:00
DearTanker
37c7507507 Style: Make all code editors apply Tailwind CSS font-mono style. 2025-06-30 13:04:47 +08:00
Mauricio Siu
ce88a0a5f2 feat: implement volume backup management functionality
- Added components for handling and displaying volume backups in the dashboard.
- Created API routes for managing volume backups, including create, update, delete, and list operations.
- Introduced database schema for volume backups, including necessary fields and relationships.
- Updated the application to integrate volume backup features into the service management interface.
2025-06-29 22:22:10 -06:00
Mauricio Siu
40f28705cb Merge branch 'canary' into 394-ability-to-backup-named-volume-to-s3 2025-06-29 21:17:05 -06:00
Mauricio Siu
03e04b7bce Merge pull request #2105 from Dokploy/fix/cancel-schedules
feat: add kill process functionality to schedules
2025-06-29 21:13:37 -06:00
Mauricio Siu
b920e7c0f1 fix: handle potential null data in PID extraction for runCommand function 2025-06-29 21:10:11 -06:00
Mauricio Siu
15fde820a3 Merge pull request #2104 from DearTanker/canary
Style: Make Node Applications more readable.
2025-06-29 21:09:29 -06:00
Mauricio Siu
64293fce79 feat: add kill process functionality to deployments
- Implemented a new mutation to kill a running deployment process by its PID.
- Updated the deployment schema to include a PID field.
- Enhanced the deployment service to handle process termination and status updates.
- Modified the deployment scripts to echo PID and schedule ID for better tracking.
- Added error handling for the kill process operation.
2025-06-29 21:08:51 -06:00
DearTanker
526f249d0e Style: Make Node Applications more readable 2025-06-30 10:54:45 +08:00
Mauricio Siu
17c5a42d8e Merge branch 'canary' into 394-ability-to-backup-named-volume-to-s3 2025-06-29 14:11:34 -06:00
autofix-ci[bot]
fac96b5db5 [autofix.ci] apply automated fixes 2025-06-29 20:11:18 +00:00
Mauricio Siu
4796b0cf4e chore: update version in package.json to v0.23.5 2025-06-29 14:09:31 -06:00
Mauricio Siu
159a055bc6 Merge pull request #2102 from Dokploy/2092-preview-link-without-protocol
fix: update preview deployment comment to include protocol in domain URL
2025-06-29 13:47:30 -06:00
Mauricio Siu
cfade317f1 fix: update preview deployment comment to include protocol in domain URL 2025-06-29 13:47:17 -06:00
Mauricio Siu
36ebefff16 Merge pull request #2083 from Marukome0743/models
chore: remove unknown `apps/models` from pnpm-workspace.yaml
2025-06-29 13:24:33 -06:00
Mauricio Siu
7cc0603078 Merge pull request #2101 from SashkaHavr/fix-docker-config-path
fix: change default DOCKER_CONFIG to a config directory instead of config.json file
2025-06-29 13:24:08 -06:00
Mauricio Siu
e0e42ac554 Merge pull request #2100 from DearTanker/canary
style: Remove the width restriction on the server name.
2025-06-29 12:44:12 -06:00
Oleksandr Havrylov
e004d8bd52 fix: change default DOCKER_CONFIG to a config directory instead of config.json file 2025-06-29 17:14:50 +02:00
DearTanker
0c0912f606 Style: Remove the width restriction on the server name so that it can be displayed in full. 2025-06-29 21:13:36 +08:00
Mauricio Siu
8d81440d9b Merge branch 'canary' into 394-ability-to-backup-named-volume-to-s3 2025-06-28 13:23:37 -06:00
Mauricio Siu
9ede3bd71b feat(rollbacks): update backup and restore instructions for N8N data
- Added detailed commands for backing up and restoring N8N data using Docker.
- Included steps for scaling services and managing permissions during the restore process.
- Enhanced clarity of backup commands and added comments for better understanding.
2025-06-28 13:17:34 -06:00
Jhonatan Caldeira
df6a72ea50 Merge branch 'Dokploy:canary' into feat/internal-path-routing 2025-06-28 12:24:01 -03:00
Mauricio Siu
e79f8c4b72 feat: add Tolgee sponsorship banner to README 2025-06-27 00:11:02 -06:00
Mauricio Siu
26e2a24f63 chore: update version in package.json to v0.23.4 2025-06-26 23:57:36 -06:00
Mauricio Siu
830feabd70 Merge pull request #2072 from dscamargo/feat/database-name-database-backup-notification
feat: add database name in database backup notification
2025-06-26 23:55:57 -06:00
Mauricio Siu
122a3d110d Merge pull request #2090 from Dokploy/fix/correct-database-type-on-compose-backups
fix(backups): enhance database type handling in compose backup process
2025-06-26 23:34:33 -06:00
Mauricio Siu
c05edb313f fix(backups): enhance database type handling in compose backup process
- Added a new function to determine the database type based on the backup configuration.
- Updated notifications to use the correct database type instead of a hardcoded value.
2025-06-26 23:32:18 -06:00
Mauricio Siu
1ec2853862 Merge pull request #2089 from Dokploy/2080-invalid-name-when-trying-to-deploy-an-app-with-dockerfile-to-ghcrio
fix(upload): refactor registry tag construction for image uploads
2025-06-26 23:02:36 -06:00
Mauricio Siu
5c2709248c fix(upload): refactor registry tag construction for image uploads
- Updated the logic for constructing the registry tag in the uploadImage and uploadImageRemoteCommand functions to ensure correct formatting.
- Removed unnecessary path imports and streamlined the code for better readability.
2025-06-26 23:00:51 -06:00
autofix-ci[bot]
bc79074441 [autofix.ci] apply automated fixes 2025-06-27 03:13:41 +00:00
Mauricio Siu
3b5428697b feat(rollbacks): add backup and restore instructions for license-named backups
- Introduced a new Backup file containing detailed steps for creating and restoring backups using Docker.
- Included commands for stopping services, removing volumes, and managing backup files.
2025-06-26 21:13:09 -06:00
Mauricio Siu
5ada451916 Merge pull request #2087 from Dokploy/feat/add-dokploy-cloud-endpoints
feat(settings): add query to retrieve Dokploy cloud IPs
2025-06-26 21:01:50 -06:00
Mauricio Siu
6b0d9240dd feat(settings): add query to retrieve Dokploy cloud IPs
- Implemented a new admin procedure to fetch cloud IPs from environment variables.
- Returns an empty array if not in cloud mode.
2025-06-26 21:00:51 -06:00
zuohuadong
475c452451 chore(*): use dnf 2025-06-26 16:05:56 +08:00
Marukome0743
1e31ebb9c2 chore: remove unknown apps/models from pnpm-workspace.yaml 2025-06-26 16:49:44 +09:00
zuohuadong
207fe6f477 feat(swap): On hosts with less than 2G of memory, increase swap space to prevent installation failure. 2025-06-26 10:32:23 +08:00
zuohuadong
0b34676336 chore (opencloud) fix docker install 2025-06-26 10:24:14 +08:00
Douglas Camargo
499022a328 feat: add database name in database backup notification 2025-06-23 22:35:40 -03:00
Mauricio Siu
fb5d2bd5b6 feat(docker-swarm): implement server authorization checks and input validation
- Added server authorization checks to ensure users can only access resources for their organization.
- Enhanced input validation for container and app names using regex to prevent invalid entries.
- Updated multiple query procedures in both docker and swarm routers to include these checks and validations.
2025-06-22 23:57:12 -06:00
Mauricio Siu
e42f6bc610 feat(user-validation): enhance path validation in Traefik config
- Added refined validation for the 'path' field to prevent directory traversal attacks and unauthorized access.
- Implemented checks for null bytes and ensured paths start with the MAIN_TRAEFIK_PATH constant.
2025-06-22 23:57:02 -06:00
Mauricio Siu
61cf426615 feat(user-access): implement access control for user information retrieval
- Added checks to deny access if the user is not found in the organization.
- Implemented authorization logic to allow access only for users requesting their own information or users with owner role in the same organization.
2025-06-22 23:56:13 -06:00
Jhonatan Caldeira
dde12e132a Merge branch 'Dokploy:canary' into feat/internal-path-routing 2025-06-22 17:35:53 -03:00
Jhonatan Caldeira
fd0f679d0f feat(domains): add internal path routing and
strip path functionality to compose

  - Add internalPath field to route requests
  to different paths internally
  - Add stripPath option to remove external
  path prefix before forwarding
  - Improves validation for stripPath (requires
  non-root path) and internalPath (must start
  with /)
2025-06-22 14:55:27 -03:00
Mauricio Siu
2a89be6efc Merge pull request #2069 from Dokploy/2065-rollback-feature-dns-issues
feat(rollbacks): enhance fullContext type and refactor createRollback…
2025-06-22 18:01:21 +02:00
autofix-ci[bot]
412bb9e874 [autofix.ci] apply automated fixes 2025-06-22 16:00:36 +00:00
Mauricio Siu
6290c217f1 feat(rollbacks): add alert for storage usage in rollback settings
- Introduced an AlertBlock component to inform users about increased storage usage when rollbacks are enabled.
- Added cautionary note regarding the potential deletion of rollback images during manual cache cleaning.
2025-06-22 10:00:14 -06:00
Mauricio Siu
4babdd45ea chore: update version in package.json to v0.23.3 2025-06-22 09:58:35 -06:00
Mauricio Siu
24bff96898 feat(rollbacks): enhance fullContext type and refactor createRollback logic
- Updated fullContext type in rollbacks schema to include Application and Project types.
- Refactored createRollback function to separate fullContext from input and handle it more efficiently.
- Integrated environment variable preparation into the rollback process.
2025-06-22 09:56:36 -06:00
Jhonatan Caldeira
df8f1252a0 Merge branch 'Dokploy:canary' into feat/internal-path-routing 2025-06-22 11:54:59 -03:00
Mauricio Siu
892f272108 Merge pull request #2066 from nikolajjsj/feat/reset-2fa-script
Feat/reset 2fa script
2025-06-22 16:40:47 +02:00
Mauricio Siu
fca537ee40 feat(esbuild): add entry point for reset-2fa script 2025-06-22 08:40:13 -06:00
Mauricio Siu
ae24aa8be5 Merge pull request #2067 from Dokploy/fix/envs-not-reset
fix: simplify useEffect condition in ShowEnvironment component
2025-06-22 16:32:54 +02:00
Mauricio Siu
b74d3995ee chore: update version in package.json to v0.23.2 2025-06-22 08:32:20 -06:00
Mauricio Siu
f7fd77f7e9 fix: simplify useEffect condition in ShowEnvironment component 2025-06-22 08:31:46 -06:00
nikolajjsj
db8a4e6edf feat(scripts): add command to run reset-2fa script 2025-06-22 15:11:12 +02:00
nikolajjsj
fa16cfec2a feat(scripts): add script to reset 2fa for admin
Similar style to existing reset-password script
2025-06-22 15:10:57 +02:00
Mauricio Siu
f35d084dd4 chore: update version in package.json to v0.23.1 2025-06-22 00:58:00 -06:00
Mauricio Siu
274daf52c0 Merge pull request #2062 from Dokploy/fix/migration-git-permissions
refactor(git_provider): update userId assignment to use owner_id from…
2025-06-22 08:57:10 +02:00
Mauricio Siu
da52d767eb refactor(git_provider): update userId assignment to use owner_id from organization table
- Changed the SQL update statement to directly select the owner_id from the organization table instead of joining with the account table, simplifying the query.
2025-06-22 00:52:45 -06:00
autofix-ci[bot]
8599f519a4 [autofix.ci] apply automated fixes 2025-06-22 05:44:21 +00:00
Mauricio Siu
113e4ae4b5 fix(middleware): update domain type and improve middleware configuration
- Changed the type of the `domain` parameter in `createPathMiddlewares` from `any` to `Domain` for better type safety.
- Added missing commas in middleware configuration objects to ensure proper syntax.
2025-06-21 23:43:52 -06:00
Mauricio Siu
7f0bdc7e00 feat(domain): add internalPath and stripPath properties to domain configuration
- Updated test files to include new properties `internalPath` and `stripPath` in domain configurations.
- Removed deprecated `createMultiPathDomain` function from the domain service to streamline the codebase.
2025-06-21 23:42:18 -06:00
Mauricio Siu
b685a817fd feat(domain): add internalPath and stripPath columns to domain schema
- Introduced new columns `internalPath` (text) with a default value of '/' and `stripPath` (boolean) with a default value of false to the domain table.
- Updated journal metadata to reflect the addition of these columns.
2025-06-21 23:39:22 -06:00
Mauricio Siu
6061a443d1 Merge branch 'canary' into feat/internal-path-routing 2025-06-21 23:39:01 -06:00
Mauricio Siu
4c9835d1f3 refactor(domain): remove deprecated columns internalPath and stripPath from domain schema
- Deleted SQL statements for internalPath and stripPath columns in the domain table.
- Updated journal and snapshot metadata to reflect the removal of these columns.
2025-06-21 23:38:39 -06:00
Jhonatan Caldeira
f2671f9369 fix(domain): remove unused ApplicationNested type import 2025-06-20 17:51:05 -03:00
Jhonatan Caldeira
bb904bb011 feat: add internal path routing and path stripping for domains
- Add internalPath and stripPath fields to domain schema
- Implement UI controls for configuring internal path routing
- Create Traefik middleware for path manipulation (addPrefix/stripPrefix)
- Support different external and internal paths for applications
- Enable path stripping for cleaner URL forwarding

This allows applications to be accessed via external paths while maintaining
different internal routing structures, useful for microservices and legacy
applications that expect specific path prefixes.
2025-06-20 16:36:27 -03:00
s1nyx
9fb6ca2b3b feat(traefik-setup): check for existing image before pulling to optimize image management 2025-06-11 08:12:00 +02:00
s1nyx
9f146d7d80 fix(traefik-setup): now pulling the traefik image to make it sure it's present locally 2025-06-11 08:06:13 +02:00
Mauricio Siu
cad628d155 refactor: remove unused volume suffix function from collision utility 2025-05-10 02:58:31 -06:00
Mauricio Siu
cd8230b0e5 refactor(add-domain): remove debug log statement from AddDomain component 2025-04-06 11:14:43 -06:00
515 changed files with 59644 additions and 4269 deletions

21
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,21 @@
## 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)
Close automatically the related issues using the keywords: `closes #ISSUE_NUMBER`, `fixes #ISSUE_NUMBER`, `resolves #ISSUE_NUMBER`
Example: `closes #123`
## Screenshots (if applicable)
If you include a video or screenshot, would be awesome so we can see the changes in action.

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

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:

View File

@@ -2,7 +2,8 @@ name: Dokploy Docker Build
on:
push:
branches: [main, canary, "1061-custom-docker-service-hostname"]
branches: [main, canary]
workflow_dispatch:
env:
IMAGE_NAME: dokploy/dokploy

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

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
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["biomejs.biome"]
}

8
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
}
}

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

View File

@@ -1,8 +1,9 @@
# syntax=docker/dockerfile:1
FROM node:20.9-slim AS base
FROM node:20.16.0-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack prepare pnpm@9.12.0 --activate
FROM base AS build
COPY . /usr/src/app
@@ -57,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

View File

@@ -1,8 +1,9 @@
# syntax=docker/dockerfile:1
FROM node:20.9-slim AS base
FROM node:20.16.0-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack prepare pnpm@9.12.0 --activate
FROM base AS build
COPY . /usr/src/app

View File

@@ -1,8 +1,9 @@
# syntax=docker/dockerfile:1
FROM node:20.9-slim AS base
FROM node:20.16.0-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack prepare pnpm@9.12.0 --activate
FROM base AS build
COPY . /usr/src/app

View File

@@ -1,8 +1,9 @@
# syntax=docker/dockerfile:1
FROM node:20.9-slim AS base
FROM node:20.16.0-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack prepare pnpm@9.12.0 --activate
FROM base AS build
COPY . /usr/src/app

View File

@@ -16,28 +16,29 @@ Here's how to install docker on different operating systems:
### Ubuntu
```bash
# Uninstall old versions
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
# Update package index
sudo apt-get update
# Install prerequisites
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
# Add Docker's official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Set up stable repository
# Add the repository to Apt sources
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```
## Windows

View File

@@ -2,7 +2,7 @@
## Core License (Apache License 2.0)
Copyright 2024 Mauricio Siu.
Copyright 2025 Mauricio Siu.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

111
README.md
View File

@@ -1,23 +1,19 @@
<div align="center">
<div>
<a href="https://dokploy.com" target="_blank" rel="noopener">
<img style="object-fit: cover;" align="center" width="100%"src=".github/sponsors/logo.png" alt="Dokploy - Open Source Alternative to Vercel, Heroku and Netlify." />
</a>
</div>
</br>
<div align="center">
<div>Join us on Discord for help, feedback, and discussions!</div>
<a href="https://dokploy.com">
<img src=".github/sponsors/logo.png" alt="Dokploy - Open Source Alternative to Vercel, Heroku and Netlify." width="100%" />
</a>
</br>
</br>
<p>Join us on Discord for help, feedback, and discussions!</p>
<a href="https://discord.gg/2tBnJ3jDJc">
<img src="https://discordapp.com/api/guilds/1234073262418563112/widget.png?style=banner2" alt="Discord Shield"/>
</a>
</div>
</div>
<br />
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.
@@ -47,7 +43,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.
@@ -61,76 +57,47 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
### Hero Sponsors 🎖
<div style="display: flex; align-items: center; gap: 20px;">
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
<img src=".github/sponsors/hostinger.jpg" alt="Hostinger" height="50"/>
</a>
<a href="https://www.lxaer.com/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
<img src=".github/sponsors/lxaer.png" alt="LX Aer" height="50"/>
</a>
<a href="https://mandarin3d.com/?ref=dokploy" target="_blank" style="display: inline-block;">
<img src=".github/sponsors/mandarin.png" alt="Mandarin" height="50"/>
</a>
<a href="https://lightnode.com/?ref=dokploy" target="_blank" style="display: inline-block;">
<img src=".github/sponsors/light-node.webp" alt="Lightnode" height="70"/>
</a>
<div>
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy"><img src=".github/sponsors/hostinger.jpg" alt="Hostinger" width="300"/></a>
<a href="https://www.lxaer.com/?ref=dokploy"><img src=".github/sponsors/lxaer.png" alt="LX Aer" width="100"/></a>
</div>
<!-- Premium Supporters 🥇 -->
<!-- Add Premium Supporters here -->
### Premium Supporters 🥇
<div style="display: flex; align-items: center; gap: 20px;">
<a href="https://supafort.com/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 20px;">
<img src="https://supafort.com/build/q-4Ht4rBZR.webp" alt="Supafort.com" height="50"/>
</a>
<a href="https://agentdock.ai/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 50px;">
<img src=".github/sponsors/agentdock.png" alt="agentdock.ai" height="70"/>
</a>
<div>
<a href="https://supafort.com/?ref=dokploy"><img src="https://supafort.com/build/q-4Ht4rBZR.webp" alt="Supafort.com" width="300"/></a>
<a href="https://agentdock.ai/?ref=dokploy"><img src=".github/sponsors/agentdock.png" alt="agentdock.ai" width="100"/></a>
</div>
### Elite Contributors 🥈
<div style="display: flex; align-items: center; gap: 20px;">
<a href="https://americancloud.com/?ref=dokploy" target="_blank" style="display: inline-block; padding: 10px; border-radius: 10px;">
<img src=".github/sponsors/american-cloud.png" alt="AmericanCloud" height="70"/>
</a>
</div>
<!-- Elite Contributors 🥈 -->
<!-- Add Elite Contributors here -->
### Supporting Members 🥉
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
<a href="https://lightspeed.run/?ref=dokploy"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://cloudblast.io/?ref=dokploy "><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Cloudblast.io"/></a>
<a href="https://startupfa.me/?ref=dokploy "><img src=".github/sponsors/startupfame.png" width="65px" alt="Startupfame"/></a>
<a href="https://itsdb-center.com?ref=dokploy "><img src=".github/sponsors/its.png" width="65px" alt="Itsdb-center"/></a>
<a href="https://openalternative.co/?ref=dokploy "><img src=".github/sponsors/openalternative.png" width="65px" alt="Openalternative"/></a>
<a href="https://synexa.ai/?ref=dokploy"><img src=".github/sponsors/synexa.png" width="65px" alt="Synexa"/></a>
### Elite Contributors 🥈
<div>
<a href="https://americancloud.com/?ref=dokploy"><img src=".github/sponsors/american-cloud.png" alt="AmericanCloud" width="300"/></a>
<a href="https://tolgee.io/?utm_source=github_dokploy&utm_medium=banner&utm_campaign=dokploy"><img src="https://dokploy.com/tolgee-logo.png" alt="Tolgee" width="100"/></a>
</div>
### Supporting Members 🥉
<div>
<a href="https://cloudblast.io/?ref=dokploy"><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Cloudblast.io"/></a>
<a href="https://synexa.ai/?ref=dokploy"><img src=".github/sponsors/synexa.png" width="65px" alt="Synexa"/></a>
</div>
### Community Backers 🤝
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
<a href="https://steamsets.com/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/111978405?s=200&v=4" width="60px" alt="Steamsets.com"/></a>
<a href="https://rivo.gg/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/126797452?s=200&v=4" width="60px" alt="Rivo.gg"/></a>
<a href="https://photoquest.wedding/?ref=dokploy"><img src="https://photoquest.wedding/favicon/android-chrome-512x512.png" width="60px" alt="Rivo.gg"/></a>
</div>
#### Organizations:
[![Sponsors on Open Collective](https://opencollective.com/dokploy/organizations.svg?width=890)](https://opencollective.com/dokploy)
[Sponsors on Open Collective](https://opencollective.com/dokploy)
#### Individuals:
@@ -139,15 +106,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" />
</a>
## Video Tutorial
<a href="https://youtu.be/mznYKPvhcfw">
<img src="https://dokploy.com/banner.png" alt="Watch the video" width="400" style="border-radius:20px;"/>
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" alt="Contributors" />
</a>
## Contributing
## 📺 Video Tutorial
<a href="https://youtu.be/mznYKPvhcfw">
<img src="https://dokploy.com/banner.png" alt="Watch the video" width="400"/>
</a>
## 🤝 Contributing
Check out the [Contributing Guide](CONTRIBUTING.md) for more information.

View File

@@ -9,25 +9,30 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"inngest": "3.40.1",
"@dokploy/server": "workspace:*",
"@hono/node-server": "^1.12.1",
"@hono/node-server": "^1.14.3",
"@hono/zod-validator": "0.3.0",
"@nerimity/mimiqueue": "1.2.3",
"dotenv": "^16.3.1",
"hono": "^4.5.8",
"dotenv": "^16.4.5",
"hono": "^4.7.10",
"pino": "9.4.0",
"pino-pretty": "11.2.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"redis": "4.7.0",
"zod": "^3.23.4"
"zod": "^3.25.32"
},
"devDependencies": {
"@types/node": "^20.11.17",
"@types/node": "^20.17.51",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"tsx": "^4.7.1",
"typescript": "^5.4.2"
"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"
}
}

View File

@@ -2,21 +2,79 @@ 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 { 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,
},
{ 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 +84,55 @@ 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.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 });

View File

@@ -64,7 +64,7 @@ export const deploy = async (job: DeployJob) => {
}
}
}
} 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;

View File

@@ -1,26 +0,0 @@
# License
## Core License (Apache License 2.0)
Copyright 2024 Mauricio Siu.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
## Additional Terms for Specific Features
The following additional terms apply to the multi-node support, Docker Compose file, Preview Deployments and Multi Server features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support, Preview Deployments and Multi Server, will always be free to use in the self-hosted version.
- **Restriction on Resale**: The multi-node support, Docker Compose file support, Preview Deployments and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support, Preview Deployments and Multi Server features must be distributed freely and cannot be sold or offered as a service.
For further inquiries or permissions, please contact us directly.

View File

@@ -1,5 +1,5 @@
import { addSuffixToAllProperties } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToAllProperties } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToConfigsRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToConfigsRoot, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,8 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToConfigsInServices } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import {
addSuffixToConfigsInServices,
generateRandomHash,
} from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToAllConfigs } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToAllConfigs, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -19,6 +19,8 @@ describe("createDomainLabels", () => {
path: "/",
createdAt: "",
previewDeploymentId: "",
internalPath: "/",
stripPath: false,
};
it("should create basic labels for web entrypoint", async () => {
@@ -106,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",
);
});
});

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToNetworksRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToNetworksRoot, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,8 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNetworks } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import {
addSuffixToServiceNetworks,
generateRandomHash,
} from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,10 +1,10 @@
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";

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToSecretsRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToSecretsRoot, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,8 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToSecretsInServices } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import {
addSuffixToSecretsInServices,
generateRandomHash,
} from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,5 +1,5 @@
import { addSuffixToAllSecrets } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToAllSecrets } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,8 +1,8 @@
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";

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToServiceNames } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,9 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToAllVolumes, addSuffixToVolumesRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import {
addSuffixToAllVolumes,
addSuffixToVolumesRoot,
generateRandomHash,
} from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,5 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToVolumesRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToVolumesRoot, generateRandomHash } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,6 +1,8 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToVolumesInServices } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import {
addSuffixToVolumesInServices,
generateRandomHash,
} from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

View File

@@ -1,5 +1,5 @@
import { addSuffixToAllVolumes } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { addSuffixToAllVolumes } from "@dokploy/server";
import { load } from "js-yaml";
import { expect, test } from "vitest";

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 = {

View File

@@ -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,12 @@ if (typeof window === "undefined") {
}
const baseApp: ApplicationNested = {
railpackVersion: "0.2.2",
applicationId: "",
herokuVersion: "",
giteaBranch: "",
giteaBuildPath: "",
previewRequireCollaboratorPermissions: false,
giteaId: "",
giteaOwner: "",
giteaRepository: "",
@@ -141,7 +143,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 });

View File

@@ -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", () => {

View File

@@ -1,10 +1,9 @@
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: "",
herokuVersion: "",
@@ -18,6 +17,7 @@ const baseApp: ApplicationNested = {
appName: "",
autoDeploy: true,
enableSubmodules: false,
previewRequireCollaboratorPermissions: false,
serverId: "",
branch: null,
dockerBuildStage: "",
@@ -119,6 +119,8 @@ const baseDomain: Domain = {
domainType: "application",
uniqueConfigKey: 1,
previewDeploymentId: "",
internalPath: "/",
stripPath: false,
};
const baseRedirect: Redirect = {

View File

@@ -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";
@@ -26,12 +32,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({
@@ -130,7 +130,7 @@ const createStringToJSONSchema = (schema: z.ZodTypeAny) => {
}
try {
return JSON.parse(str);
} catch (_e) {
} catch {
ctx.addIssue({ code: "custom", message: "Invalid JSON format" });
return z.NEVER;
}
@@ -181,21 +181,38 @@ const addSwarmSettings = z.object({
type AddSwarmSettings = z.infer<typeof addSwarmSettings>;
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: {
@@ -244,7 +261,12 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
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,
@@ -270,18 +292,18 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
Swarm Settings
</Button>
</DialogTrigger>
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-5xl p-0">
<DialogHeader className="p-6">
<DialogContent className="sm:max-w-5xl">
<DialogHeader>
<DialogTitle>Swarm Settings</DialogTitle>
<DialogDescription>
Update certain settings using a json object.
</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 +311,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>
@@ -351,7 +373,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>
@@ -405,7 +427,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 +493,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>
@@ -529,7 +551,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>
@@ -587,7 +609,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 +672,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 +731,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>
@@ -753,7 +775,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
)}
/>
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2 m-0 sticky bottom-0 right-0 bg-muted border p-2 ">
<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}
form="hook-form-add-permissions"

View File

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

View File

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

View File

@@ -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(),
@@ -107,7 +107,7 @@ export const ShowImport = ({ composeId }: Props) => {
composeId,
});
setShowModal(false);
} catch (_error) {
} catch {
toast.error("Error importing template");
}
};
@@ -126,7 +126,7 @@ export const ShowImport = ({ composeId }: Props) => {
});
setTemplateInfo(result);
setShowModal(true);
} catch (_error) {
} catch {
toast.error("Error processing template");
}
};
@@ -185,7 +185,7 @@ export const ShowImport = ({ composeId }: Props) => {
</Button>
</div>
<Dialog open={showModal} onOpenChange={setShowModal}>
<DialogContent className="max-h-[80vh] max-w-[50vw] overflow-y-auto">
<DialogContent className="max-w-[50vw]">
<DialogHeader>
<DialogTitle className="text-2xl font-bold">
Template Information

View File

@@ -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,15 +32,12 @@ 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),
publishMode: z.enum(["ingress", "host"], {
required_error: "Publish mode is required",
}),
targetPort: z.number().int().min(1).max(65535),
protocol: z.enum(["tcp", "udp"], {
required_error: "Protocol is required",
@@ -77,9 +80,15 @@ export const HandlePorts = ({
resolver: zodResolver(AddPortSchema),
});
const publishMode = useWatch({
control: form.control,
name: "publishMode",
});
useEffect(() => {
form.reset({
publishedPort: data?.publishedPort ?? 0,
publishMode: data?.publishMode ?? "ingress",
targetPort: data?.targetPort ?? 0,
protocol: data?.protocol ?? "tcp",
});
@@ -120,7 +129,7 @@ export const HandlePorts = ({
<Button>{children}</Button>
)}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Ports</DialogTitle>
<DialogDescription>
@@ -165,6 +174,32 @@ export const HandlePorts = ({
</FormItem>
)}
/>
<FormField
control={form.control}
name="publishMode"
render={({ field }) => {
return (
<FormItem className="md:col-span-2">
<FormLabel>Published Port Mode</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a publish mode for the port" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value={"ingress"}>Ingress</SelectItem>
<SelectItem value={"host"}>Host</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="targetPort"
@@ -223,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}

View File

@@ -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;
}
@@ -60,7 +61,7 @@ export const ShowPorts = ({ applicationId }: Props) => {
{data?.ports.map((port) => (
<div key={port.portId}>
<div className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 flex-col gap-4 sm:gap-8">
<div className="flex flex-col gap-1">
<span className="font-medium">Published Port</span>
<span className="text-sm text-muted-foreground">
@@ -68,7 +69,13 @@ export const ShowPorts = ({ applicationId }: Props) => {
</span>
</div>
<div className="flex flex-col gap-1">
<span className="font-medium"> Target Port</span>
<span className="font-medium">Published Port Mode</span>
<span className="text-sm text-muted-foreground">
{port?.publishMode?.toUpperCase()}
</span>
</div>
<div className="flex flex-col gap-1">
<span className="font-medium">Target Port</span>
<span className="text-sm text-muted-foreground">
{port.targetPort}
</span>

View File

@@ -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"),
@@ -179,7 +179,7 @@ export const HandleRedirect = ({
<Button>{children}</Button>
)}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Redirects</DialogTitle>
<DialogDescription>

View File

@@ -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 {

View File

@@ -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"),
@@ -114,7 +114,7 @@ export const HandleSecurity = ({
<Button>{children}</Button>
)}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Security</DialogTitle>
<DialogDescription>
@@ -151,7 +151,7 @@ export const HandleSecurity = ({
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input placeholder="test" {...field} />
<Input placeholder="test" type="password" {...field} />
</FormControl>
<FormMessage />

View File

@@ -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">

View File

@@ -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(),

View File

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

View File

@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import jsyaml from "js-yaml";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { 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(),
@@ -122,7 +122,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
<DialogTrigger asChild>
<Button isLoading={isLoading}>Modify</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-4xl">
<DialogContent className="sm:max-w-4xl">
<DialogHeader>
<DialogTitle>Update traefik config</DialogTitle>
<DialogDescription>Update the traefik config</DialogDescription>

View File

@@ -1,3 +1,11 @@
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";
import {
@@ -21,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:
@@ -150,7 +152,7 @@ export const AddVolumes = ({
<DialogTrigger className="" asChild>
<Button>{children}</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-3xl">
<DialogContent className="sm:max-w-3xl">
<DialogHeader>
<DialogTitle>Volumes / Mounts</DialogTitle>
</DialogHeader>
@@ -169,6 +171,23 @@ export const AddVolumes = ({
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-8 "
>
{type === "bind" && (
<AlertBlock>
<div className="space-y-2">
<p>
Make sure the host path is a valid path and exists in the
host machine.
</p>
<p className="text-sm text-muted-foreground">
<strong>Cluster Warning:</strong> If you're using cluster
features, bind mounts may cause deployment failures since
the path must exist on all worker/manager nodes. Consider
using external tools to distribute the folder across nodes
or use named volumes instead.
</p>
</div>
</AlertBlock>
)}
<FormField
control={form.control}
defaultValue={form.control._defaultValues.type}

View File

@@ -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

View File

@@ -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"),
@@ -186,7 +186,7 @@ export const UpdateVolume = ({
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-3xl">
<DialogContent className="sm:max-w-3xl">
<DialogHeader>
<DialogTitle>Update</DialogTitle>
<DialogDescription>Update the mount</DialogDescription>

View File

@@ -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

View File

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

View File

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

View File

@@ -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";
@@ -124,7 +124,7 @@ export const ShowDeployment = ({
}
}}
>
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}>
<DialogContent className={"sm:max-w-5xl"}>
<DialogHeader>
<DialogTitle>Deployment</DialogTitle>
<DialogDescription className="flex items-center gap-2">

View File

@@ -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";
@@ -14,7 +13,8 @@ interface Props {
| "schedule"
| "server"
| "backup"
| "previewDeployment";
| "previewDeployment"
| "volumeBackup";
serverId?: string;
refreshToken?: string;
children?: React.ReactNode;
@@ -49,7 +49,7 @@ export const ShowDeploymentsModal = ({
</Button>
)}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl p-0">
<DialogContent className="sm:max-w-5xl p-0">
<ShowDeployments
id={id}
type={type}

View File

@@ -1,4 +1,8 @@
import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react";
import React, { useEffect, useState } from "react";
import { toast } from "sonner";
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 +13,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;
@@ -27,7 +27,8 @@ interface Props {
| "schedule"
| "server"
| "backup"
| "previewDeployment";
| "previewDeployment"
| "volumeBackup";
refreshToken?: string;
serverId?: string;
}
@@ -62,6 +63,8 @@ export const ShowDeployments = ({
const { mutateAsync: rollback, isLoading: isRollingBack } =
api.rollback.rollback.useMutation();
const { mutateAsync: killProcess, isLoading: isKillingProcess } =
api.deployment.killProcess.useMutation();
const [url, setUrl] = React.useState("");
useEffect(() => {
@@ -170,6 +173,32 @@ export const ShowDeployments = ({
</div>
<div className="flex flex-row items-center gap-2">
{deployment.pid && deployment.status === "running" && (
<DialogAction
title="Kill Process"
description="Are you sure you want to kill the process?"
type="default"
onClick={async () => {
await killProcess({
deploymentId: deployment.deploymentId,
})
.then(() => {
toast.success("Process killed successfully");
})
.catch(() => {
toast.error("Error killing process");
});
}}
>
<Button
variant="destructive"
size="sm"
isLoading={isKillingProcess}
>
Kill Process
</Button>
</DialogAction>
)}
<Button
onClick={() => {
setActiveLog(deployment);

View File

@@ -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: {
@@ -33,7 +33,7 @@ export const DnsHelperModal = ({ domain, serverIp }: Props) => {
<HelpCircle className="size-4" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogContent className="sm:max-w-2xl">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Server className="size-5" />

View File

@@ -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";
@@ -49,6 +48,8 @@ export const domain = z
.object({
host: z.string().min(1, { message: "Add a hostname" }),
path: z.string().min(1).optional(),
internalPath: z.string().optional(),
stripPath: z.boolean().optional(),
port: z
.number()
.min(1, { message: "Port must be at least 1" })
@@ -84,6 +85,29 @@ export const domain = z
message: "Required",
});
}
// Validate stripPath requires a valid path
if (input.stripPath && (!input.path || input.path === "/")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["stripPath"],
message:
"Strip path can only be enabled when a path other than '/' is specified",
});
}
// Validate internalPath starts with /
if (
input.internalPath &&
input.internalPath !== "/" &&
!input.internalPath.startsWith("/")
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["internalPath"],
message: "Internal path must start with '/'",
});
}
});
type Domain = z.infer<typeof domain>;
@@ -98,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(
@@ -162,6 +187,8 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
defaultValues: {
host: "",
path: undefined,
internalPath: undefined,
stripPath: false,
port: undefined,
https: false,
certificateType: undefined,
@@ -182,6 +209,8 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
...data,
/* Convert null to undefined */
path: data?.path || undefined,
internalPath: data?.internalPath || undefined,
stripPath: data?.stripPath || false,
port: data?.port || undefined,
certificateType: data?.certificateType || undefined,
customCertResolver: data?.customCertResolver || undefined,
@@ -194,6 +223,8 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
form.reset({
host: "",
path: undefined,
internalPath: undefined,
stripPath: false,
port: undefined,
https: false,
certificateType: undefined,
@@ -261,7 +292,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
<DialogTrigger className="" asChild>
{children}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogContent className="sm:max-w-2xl">
<DialogHeader>
<DialogTitle>Domain</DialogTitle>
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
@@ -294,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
@@ -342,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>
@@ -469,6 +549,49 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
}}
/>
<FormField
control={form.control}
name="internalPath"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Internal Path</FormLabel>
<FormDescription>
The path where your application expects to receive
requests internally (defaults to "/")
</FormDescription>
<FormControl>
<Input placeholder={"/"} {...field} />
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="stripPath"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between p-3 border rounded-lg shadow-sm">
<div className="space-y-0.5">
<FormLabel>Strip Path</FormLabel>
<FormDescription>
Remove the external path from the request before
forwarding to the application
</FormDescription>
<FormMessage />
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="port"

View File

@@ -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";

View File

@@ -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({

View File

@@ -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(),
@@ -49,7 +49,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
currentBuildArgs !== (data?.buildArgs || "");
useEffect(() => {
if (data && !hasChanges) {
if (data) {
form.reset({
env: data.env || "",
buildArgs: data.buildArgs || "",

View 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({
buildPath: z.string().min(1, "Path is required").default("/"),

View File

@@ -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, {

View File

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

View File

@@ -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("/"),

View File

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

View File

@@ -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("/"),

View File

@@ -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 } 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("/"),
@@ -96,6 +96,16 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
const repository = form.watch("repository");
const gitlabId = form.watch("gitlabId");
const gitlabUrl = useMemo(() => {
const url = gitlabProviders?.find(
(provider) => provider.gitlabId === gitlabId,
)?.gitlabUrl;
const gitlabUrl = url?.replace(/\/$/, "");
return gitlabUrl || "https://gitlab.com";
}, [gitlabId, gitlabProviders]);
const {
data: repositories,
isLoading: isLoadingRepositories,
@@ -224,7 +234,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
<FormLabel>Repository</FormLabel>
{field.value.owner && field.value.repo && (
<Link
href={`https://gitlab.com/${field.value.owner}/${field.value.repo}`}
href={`${gitlabUrl}/${field.value.owner}/${field.value.repo}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
@@ -278,7 +288,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
{repositories?.map((repo) => {
return (
<CommandItem
value={repo.name}
value={repo.url}
key={repo.url}
onSelect={() => {
form.setValue("repository", {
@@ -299,7 +309,8 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
repo.name === field.value.repo
repo.url ===
field.value.gitlabPathNamespace
? "opacity-100"
: "opacity-0",
)}

View File

@@ -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";
@@ -153,8 +153,8 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
setSab(e as TabState);
}}
>
<div className="flex flex-row items-center justify-between w-full gap-4">
<TabsList className="md:grid md:w-fit md:grid-cols-7 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden">
<div className="flex flex-row items-center justify-between w-full overflow-auto">
<TabsList className="flex gap-4 justify-start bg-transparent">
<TabsTrigger
value="github"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"

View File

@@ -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:

View File

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

View File

@@ -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(

View File

@@ -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>;
@@ -138,7 +137,7 @@ export const AddPreviewDomain = ({
<DialogTrigger className="" asChild>
{children}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogContent className="sm:max-w-2xl">
<DialogHeader>
<DialogTitle>Domain</DialogTitle>
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>

View File

@@ -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";

View File

@@ -1,3 +1,9 @@
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";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -28,12 +34,6 @@ import {
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Settings2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const schema = z
.object({
@@ -46,6 +46,7 @@ const schema = z
previewPath: z.string(),
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
previewCustomCertResolver: z.string().optional(),
previewRequireCollaboratorPermissions: z.boolean(),
})
.superRefine((input, ctx) => {
if (
@@ -83,6 +84,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewHttps: false,
previewPath: "/",
previewCertificateType: "none",
previewRequireCollaboratorPermissions: true,
},
resolver: zodResolver(schema),
});
@@ -105,6 +107,8 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewPath: data.previewPath || "/",
previewCertificateType: data.previewCertificateType || "none",
previewCustomCertResolver: data.previewCustomCertResolver || "",
previewRequireCollaboratorPermissions:
data.previewRequireCollaboratorPermissions || true,
});
}
}, [data]);
@@ -121,6 +125,8 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewPath: formData.previewPath,
previewCertificateType: formData.previewCertificateType,
previewCustomCertResolver: formData.previewCustomCertResolver,
previewRequireCollaboratorPermissions:
formData.previewRequireCollaboratorPermissions,
})
.then(() => {
toast.success("Preview Deployments settings updated");
@@ -138,7 +144,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
Configure
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl w-full">
<DialogContent className="sm:max-w-5xl w-full">
<DialogHeader>
<DialogTitle>Preview Deployment Settings</DialogTitle>
<DialogDescription>
@@ -312,6 +318,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"

View File

@@ -0,0 +1,108 @@
Backup
# license-namedbackups-abxelc
1. docker ps --filter "label=com.docker.swarm.service.name=license-namedbackups-abxelc" --format "{{.Names}}"
2. docker run --rm \
--volumes-from "license-namedbackups-abxelc.1.m3cxy78ocj3w0zu42kmgamc5y" \
-v $(pwd):/backup \
ubuntu \
tar cvf /backup/backup.tar /var/lib/postgresql/data
# Official Command Backup
1. Backup
docker run --rm \
-v license-namedbackups-abxelc-data:/volume_data \
-v $(pwd):/backup \
ubuntu \
bash -c "cd /volume_data && tar cvf /backup/generic_backup.tar ."
2. Restore
docker service scale license-namedbackups-abxelc=0
docker volume rm license-namedbackups-abxelc-data
2. docker run --rm \
-v license-namedbackups-abxelc-data:/volume_data \
-v $(pwd):/backup \
ubuntu \
bash -c "cd /volume_data && tar xvf /backup/generic_backup.tar ."
docker service scale license-namedbackups-abxelc=1
root@srv594061:~# docker volume inspect n8n_data-data
[
{
"CreatedAt": "2025-06-28T18:07:44Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/n8n_data-data/_data",
"Name": "n8n_data-data",
"Options": null,
"Scope": "local"
}
]
Archivos funcuionando creados por N8N
# root@srv594061:~# cd /var/lib/docker/volumes/n8n_data-data/_data
# root@srv594061:/var/lib/docker/volumes/n8n_data-data/_data# ls
# binaryData config crash.journal database.sqlite git n8nEventLog.log ssh
Luego que intente hacer el backup con el comando de backup
root@srv594061:~# docker run --rm -v n8n_data-data:/volume_data -v $(pwd):/backup ubuntu bash -c "cd /volume_data && tar cvf /backup/generic_backup6.tar ."
./
./config
./crash.journal
./binaryData/
./git/
./database.sqlite
./ssh/
./n8nEventLog.log
root@srv594061:~#
# Paramos la aplicacion
docker service scale n8n=0
# Haciendo el restore
root@srv594061:~# docker volume rm n8n_data-data
n8n_data-data
root@srv594061:~# docker run --rm -v n8n_data-data:/volume_data -v $(pwd):/backup ubuntu bash -c "cd /volume_data && tar xvf /backup/generic_backup6.tar && chown -R 999:999 ."
./
./config
./crash.journal
./binaryData/
./git/
./database.sqlite
./ssh/
./n8nEventLog.log
# Tenemos los archivos en el volumen
root@srv594061:~# ls /var/lib/docker/volumes/n8n_data-data/_data
binaryData config crash.journal database.sqlite git n8nEventLog.log ssh
root@srv594061:~#
docker service scale n8n=1
# Luego en N8N Cuando se que el volumen tiene la data
Permissions 0644 for n8n settings file /home/node/.n8n/config are too wide. This is ignored for now, but in the future n8n will attempt to change the permissions automatically. To automatically enforce correct permissions now set N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true (recommended), or turn this check off set N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false.
User settings loaded from: /home/node/.n8n/config
Last session crashed
Error: EACCES: permission denied, open '/home/node/.n8n/crash.journal'
at open (node:internal/fs/promises:639:25)
at touchFile (/usr/local/lib/node_modules/n8n/dist/crash-journal.js:18:20)
at Object.init (/usr/local/lib/node_modules/n8n/dist/crash-journal.js:32:5)
at Start.initCrashJournal (/usr/local/lib/node_modules/n8n/dist/commands/base-command.js:113:9)
at Start.init (/usr/local/lib/node_modules/n8n/dist/commands/start.js:141:9)
at Start._run (/usr/local/lib/node_modules/n8n/node_modules/@oclif/core/lib/command.js:301:13)
at Config.runCommand (/usr/local/lib/node_modules/n8n/node_modules/@oclif/core/lib/config/config.js:424:25)
at run (/usr/local/lib/node_modules/n8n/node_modules/@oclif/core/lib/main.js:94:16)
at /usr/local/lib/node_modules/n8n/bin/n8n:71:2
TypeError: Cannot read properties of undefined (reading 'error')

View File

@@ -1,3 +1,9 @@
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 {
Dialog,
@@ -17,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(),
@@ -79,6 +80,11 @@ export const ShowRollbackSettings = ({ applicationId, children }: Props) => {
<DialogDescription>
Configure how rollbacks work for this application
</DialogDescription>
<AlertBlock>
Having rollbacks enabled increases storage usage. Be careful with
this option. Note that manually cleaning the cache may delete
rollback images, making them unavailable for future rollbacks.
</AlertBlock>
</DialogHeader>
<Form {...form}>

View File

@@ -1,9 +1,22 @@
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 { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
@@ -34,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 = [
@@ -232,14 +233,17 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
</DialogTrigger>
<DialogContent
className={cn(
"max-h-screen overflow-y-auto",
scheduleTypeForm === "dokploy-server" || scheduleTypeForm === "server"
? "max-h-[95vh] sm:max-w-2xl"
: " sm:max-w-lg",
? "sm:max-w-2xl"
: "sm:max-w-lg",
)}
>
<DialogHeader>
<DialogTitle>{scheduleId ? "Edit" : "Create"} Schedule</DialogTitle>
<DialogDescription>
{scheduleId ? "Manage" : "Create"} a schedule to run a task at a
specific time or interval.
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">

View File

@@ -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";
@@ -91,7 +91,7 @@ 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 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">
@@ -166,12 +166,16 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
await runManually({
scheduleId: schedule.scheduleId,
}).then(async () => {
await new Promise((resolve) =>
setTimeout(resolve, 1500),
);
refetchSchedules();
});
})
.then(async () => {
await new Promise((resolve) =>
setTimeout(resolve, 1500),
);
refetchSchedules();
})
.catch(() => {
toast.error("Error running schedule");
});
}}
>
<Play className="size-4 transition-colors" />

View File

@@ -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, {
@@ -99,7 +99,7 @@ export const UpdateApplication = ({ applicationId }: Props) => {
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Modify Application</DialogTitle>
<DialogDescription>Update the application data</DialogDescription>

View File

@@ -0,0 +1,672 @@
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 { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import type { CacheType } from "../domains/handle-domain";
import { commonCronExpressions } from "../schedules/handle-schedules";
const formSchema = z
.object({
name: z.string().min(1, "Name is required"),
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(),
turnOff: z.boolean().default(false),
enabled: z.boolean().default(true),
serviceType: z.enum([
"application",
"compose",
"postgres",
"mariadb",
"mongo",
"mysql",
"redis",
]),
serviceName: z.string(),
destinationId: z.string().min(1, "Destination required"),
})
.superRefine((data, ctx) => {
if (data.serviceType === "compose" && !data.serviceName) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Service name is required",
path: ["serviceName"],
});
}
if (data.serviceType === "compose" && !data.serviceName) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Service name is required",
path: ["serviceName"],
});
}
});
interface Props {
id?: string;
volumeBackupId?: string;
volumeBackupType?:
| "application"
| "compose"
| "postgres"
| "mariadb"
| "mongo"
| "mysql"
| "redis";
}
export const HandleVolumeBackups = ({
id,
volumeBackupId,
volumeBackupType,
}: 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),
defaultValues: {
name: "",
cronExpression: "",
volumeName: "",
prefix: "",
// keepLatestCount: undefined,
turnOff: false,
enabled: true,
serviceName: "",
serviceType: volumeBackupType,
},
});
const serviceTypeForm = volumeBackupType;
const { data: destinations } = api.destination.all.useQuery();
const { data: volumeBackup } = api.volumeBackups.one.useQuery(
{ volumeBackupId: volumeBackupId || "" },
{ enabled: !!volumeBackupId },
);
const { data: mounts } = api.mounts.allNamedByApplicationId.useQuery(
{ applicationId: id || "" },
{ enabled: !!id && volumeBackupType === "application" },
);
const {
data: services,
isFetching: isLoadingServices,
error: errorServices,
refetch: refetchServices,
} = api.compose.loadServices.useQuery(
{
composeId: id || "",
type: cacheType,
},
{
retry: false,
refetchOnWindowFocus: false,
enabled: !!id && volumeBackupType === "compose",
},
);
const serviceName = form.watch("serviceName");
const { data: mountsByService } = api.compose.loadMountsByService.useQuery(
{
composeId: id || "",
serviceName,
},
{
enabled: !!id && volumeBackupType === "compose" && !!serviceName,
},
);
useEffect(() => {
if (volumeBackupId && volumeBackup) {
form.reset({
name: volumeBackup.name,
cronExpression: volumeBackup.cronExpression,
volumeName: volumeBackup.volumeName || "",
prefix: volumeBackup.prefix,
// keepLatestCount: volumeBackup.keepLatestCount || undefined,
turnOff: volumeBackup.turnOff,
enabled: volumeBackup.enabled || false,
serviceName: volumeBackup.serviceName || "",
destinationId: volumeBackup.destinationId,
serviceType: volumeBackup.serviceType,
});
}
}, [form, volumeBackup, volumeBackupId]);
const { mutateAsync, isLoading } = volumeBackupId
? api.volumeBackups.update.useMutation()
: api.volumeBackups.create.useMutation();
const onSubmit = async (values: z.infer<typeof formSchema>) => {
if (!id && !volumeBackupId) return;
await mutateAsync({
...values,
destinationId: values.destinationId,
volumeBackupId: volumeBackupId || "",
serviceType: volumeBackupType,
...(volumeBackupType === "application" && {
applicationId: id || "",
}),
...(volumeBackupType === "compose" && {
composeId: id || "",
}),
...(volumeBackupType === "postgres" && {
serverId: id || "",
}),
...(volumeBackupType === "postgres" && {
postgresId: id || "",
}),
...(volumeBackupType === "mariadb" && {
mariadbId: id || "",
}),
...(volumeBackupType === "mongo" && {
mongoId: id || "",
}),
...(volumeBackupType === "mysql" && {
mysqlId: id || "",
}),
...(volumeBackupType === "redis" && {
redisId: id || "",
}),
})
.then(() => {
toast.success(
`Volume backup ${volumeBackupId ? "updated" : "created"} successfully`,
);
utils.volumeBackups.list.invalidate({
id,
volumeBackupType,
});
setIsOpen(false);
})
.catch((error) => {
toast.error(
error instanceof Error ? error.message : "An unknown error occurred",
);
});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
{volumeBackupId ? (
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10"
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
) : (
<Button>
<PlusCircle className="w-4 h-4 mr-2" />
Add Volume Backup
</Button>
)}
</DialogTrigger>
<DialogContent
className={cn(
"overflow-y-auto",
volumeBackupType === "compose" || volumeBackupType === "application"
? "max-h-[95vh] sm:max-w-2xl"
: " sm:max-w-lg",
)}
>
<DialogHeader>
<DialogTitle>
{volumeBackupId ? "Edit" : "Create"} Volume Backup
</DialogTitle>
<DialogDescription>
Create a volume backup to backup your volume to a destination
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center gap-2">
Task Name
</FormLabel>
<FormControl>
<Input placeholder="Daily Database Backup" {...field} />
</FormControl>
<FormDescription>
A descriptive name for your scheduled task
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
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>
)}
/>
<FormField
control={form.control}
name="destinationId"
render={({ field }) => (
<FormItem>
<FormLabel>Destination</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a destination" />
</SelectTrigger>
</FormControl>
<SelectContent>
{destinations?.map((destination) => (
<SelectItem
key={destination.destinationId}
value={destination.destinationId}
>
{destination.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormDescription>
Choose the backup destination where files will be stored
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{serviceTypeForm === "compose" && (
<>
<div className="flex flex-col w-full gap-4">
{errorServices && (
<AlertBlock
type="warning"
className="[overflow-wrap:anywhere]"
>
{errorServices?.message}
</AlertBlock>
)}
<FormField
control={form.control}
name="serviceName"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Service Name</FormLabel>
<div className="flex gap-2">
<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}
</SelectItem>
))}
<SelectItem value="none" disabled>
Empty
</SelectItem>
</SelectContent>
</Select>
<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>
</div>
<FormMessage />
</FormItem>
)}
/>
</div>
{mountsByService && mountsByService.length > 0 && (
<FormField
control={form.control}
name="volumeName"
render={({ field }) => (
<FormItem>
<FormLabel>Volumes</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a volume name" />
</SelectTrigger>
</FormControl>
<SelectContent>
{mountsByService?.map((volume) => (
<SelectItem
key={volume.Name}
value={volume.Name || ""}
>
{volume.Name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormDescription>
Choose the volume to backup, if you dont see the
volume here, you can type the volume name manually
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)}
</>
)}
{serviceTypeForm === "application" && (
<FormField
control={form.control}
name="volumeName"
render={({ field }) => (
<FormItem>
<FormLabel>Volumes</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a volume name" />
</SelectTrigger>
</FormControl>
<SelectContent>
{mounts?.map((mount) => (
<SelectItem key={mount.Name} value={mount.Name || ""}>
{mount.Name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormDescription>
Choose the volume to backup, if you dont see the volume
here, you can type the volume name manually
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)}
<FormField
control={form.control}
name="volumeName"
render={({ field }) => (
<FormItem>
<FormLabel>Volume Name</FormLabel>
<FormControl>
<Input placeholder="my-volume-name" {...field} />
</FormControl>
<FormDescription>
The name of the Docker volume to backup
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="prefix"
render={({ field }) => (
<FormItem>
<FormLabel>Backup Prefix</FormLabel>
<FormControl>
<Input placeholder="backup-" {...field} />
</FormControl>
<FormDescription>
Prefix for backup files (optional)
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* <FormField
control={form.control}
name="keepLatestCount"
render={({ field }) => (
<FormItem>
<FormLabel>Keep Latest Count</FormLabel>
<FormControl>
<Input
type="number"
placeholder="5"
{...field}
onChange={(e) =>
field.onChange(Number(e.target.value) || undefined)
}
/>
</FormControl>
<FormDescription>
Number of backup files to keep (optional)
</FormDescription>
<FormMessage />
</FormItem>
)}
/> */}
<FormField
control={form.control}
name="turnOff"
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center gap-2">
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
Turn Off Container During Backup
</FormLabel>
<FormDescription className="text-amber-600 dark:text-amber-400">
The container will be temporarily stopped during backup to
prevent file corruption. This ensures data integrity but may
cause temporary service interruption.
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name="enabled"
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center gap-2">
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
Enabled
</FormLabel>
</FormItem>
)}
/>
<Button type="submit" isLoading={isLoading} className="w-full">
{volumeBackupId ? "Update" : "Create"} Volume Backup
</Button>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,411 @@
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";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { formatBytes } from "../../database/backups/restore-backup";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
interface Props {
id: string;
type: "application" | "compose";
serverId?: string;
}
const RestoreBackupSchema = z.object({
destinationId: z
.string({
required_error: "Please select a destination",
})
.min(1, {
message: "Destination is required",
}),
backupFile: z
.string({
required_error: "Please select a backup file",
})
.min(1, {
message: "Backup file is required",
}),
volumeName: z
.string({
required_error: "Please enter a volume name",
})
.min(1, {
message: "Volume name is required",
}),
});
export const RestoreVolumeBackups = ({ id, type, serverId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [search, setSearch] = useState("");
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
const { data: destinations = [] } = api.destination.all.useQuery();
const form = useForm<z.infer<typeof RestoreBackupSchema>>({
defaultValues: {
destinationId: "",
backupFile: "",
volumeName: "",
},
resolver: zodResolver(RestoreBackupSchema),
});
const destinationId = form.watch("destinationId");
const volumeName = form.watch("volumeName");
const backupFile = form.watch("backupFile");
const debouncedSetSearch = debounce((value: string) => {
setDebouncedSearchTerm(value);
}, 350);
const handleSearchChange = (value: string) => {
setSearch(value);
debouncedSetSearch(value);
};
const { data: files = [], isLoading } = api.backup.listBackupFiles.useQuery(
{
destinationId: destinationId,
search: debouncedSearchTerm,
serverId: serverId ?? "",
},
{
enabled: isOpen && !!destinationId,
},
);
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
const [isDeploying, setIsDeploying] = useState(false);
api.volumeBackups.restoreVolumeBackupWithLogs.useSubscription(
{
id,
serviceType: type,
serverId,
destinationId,
volumeName,
backupFileName: backupFile,
},
{
enabled: isDeploying,
onData(log) {
if (!isDrawerOpen) {
setIsDrawerOpen(true);
}
if (log === "Restore completed successfully!") {
setIsDeploying(false);
}
const parsedLogs = parseLogs(log);
setFilteredLogs((prev) => [...prev, ...parsedLogs]);
},
onError(error) {
console.error("Restore logs error:", error);
setIsDeploying(false);
},
},
);
const onSubmit = async () => {
setIsDeploying(true);
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="outline">
<RotateCcw className="mr-2 size-4" />
Restore Volume Backup
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center">
<RotateCcw className="mr-2 size-4" />
Restore Volume Backup
</DialogTitle>
<DialogDescription>
Select a destination and search for volume backup files
</DialogDescription>
<AlertBlock>
Make sure the volume name is not being used by another container.
</AlertBlock>
</DialogHeader>
<Form {...form}>
<form
id="hook-form-restore-backup"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="destinationId"
render={({ field }) => (
<FormItem className="">
<FormLabel>Destination</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
)}
>
{field.value
? destinations.find(
(d) => d.destinationId === field.value,
)?.name
: "Select Destination"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput
placeholder="Search destinations..."
className="h-9"
/>
<CommandEmpty>No destinations found.</CommandEmpty>
<ScrollArea className="h-64">
<CommandGroup>
{destinations.map((destination) => (
<CommandItem
value={destination.destinationId}
key={destination.destinationId}
onSelect={() => {
form.setValue(
"destinationId",
destination.destinationId,
);
}}
>
{destination.name}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
destination.destinationId === field.value
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
</Command>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="backupFile"
render={({ field }) => (
<FormItem className="">
<FormLabel className="flex items-center">
Search Backup Files
{field.value && (
<Badge variant="outline" className="truncate w-52">
{field.value}
<Copy
className="ml-2 size-4 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
copy(field.value);
toast.success("Backup file copied to clipboard");
}}
/>
</Badge>
)}
</FormLabel>
<Popover modal>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
)}
>
<span className="truncate text-left flex-1 w-52">
{field.value || "Search and select a backup file"}
</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput
placeholder="Search backup files..."
value={search}
onValueChange={handleSearchChange}
className="h-9"
/>
{isLoading ? (
<div className="py-6 text-center text-sm">
Loading backup files...
</div>
) : files.length === 0 && search ? (
<div className="py-6 text-center text-sm text-muted-foreground">
No backup files found for "{search}"
</div>
) : files.length === 0 ? (
<div className="py-6 text-center text-sm text-muted-foreground">
No backup files available
</div>
) : (
<ScrollArea className="h-64">
<CommandGroup className="w-96">
{files?.map((file) => (
<CommandItem
value={file.Path}
key={file.Path}
onSelect={() => {
form.setValue("backupFile", file.Path);
if (file.IsDir) {
setSearch(`${file.Path}/`);
setDebouncedSearchTerm(`${file.Path}/`);
} else {
setSearch(file.Path);
setDebouncedSearchTerm(file.Path);
}
}}
>
<div className="flex w-full flex-col gap-1">
<div className="flex w-full justify-between">
<span className="font-medium">
{file.Path}
</span>
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
file.Path === field.value
? "opacity-100"
: "opacity-0",
)}
/>
</div>
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span>
Size: {formatBytes(file.Size)}
</span>
{file.IsDir && (
<span className="text-blue-500">
Directory
</span>
)}
{file.Hashes?.MD5 && (
<span>MD5: {file.Hashes.MD5}</span>
)}
</div>
</div>
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
)}
</Command>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="volumeName"
render={({ field }) => (
<FormItem>
<FormLabel>Volume Name</FormLabel>
<FormControl>
<Input placeholder="Enter volume name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button
isLoading={isDeploying}
form="hook-form-restore-backup"
type="submit"
// disabled={
// !form.watch("backupFile") ||
// (backupType === "compose" && !form.watch("databaseType"))
// }
>
Restore
</Button>
</DialogFooter>
</form>
</Form>
<DrawerLogs
isOpen={isDrawerOpen}
onClose={() => {
setIsDrawerOpen(false);
setFilteredLogs([]);
setIsDeploying(false);
// refetch();
}}
filteredLogs={filteredLogs}
/>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,250 @@
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";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
import { HandleVolumeBackups } from "./handle-volume-backups";
import { RestoreVolumeBackups } from "./restore-volume-backups";
interface Props {
id: string;
type?: "application" | "compose";
serverId?: string;
}
export const ShowVolumeBackups = ({
id,
type = "application",
serverId,
}: Props) => {
const {
data: volumeBackups,
isLoading: isLoadingVolumeBackups,
refetch: refetchVolumeBackups,
} = api.volumeBackups.list.useQuery(
{
id: id || "",
volumeBackupType: type,
},
{
enabled: !!id,
},
);
const utils = api.useUtils();
const { mutateAsync: deleteVolumeBackup, isLoading: isDeleting } =
api.volumeBackups.delete.useMutation();
const { mutateAsync: runManually, isLoading } =
api.volumeBackups.runManually.useMutation();
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 flex-col gap-2">
<CardTitle className="text-xl font-bold flex items-center gap-2">
Volume Backups
</CardTitle>
<CardDescription>
Schedule volume backups to run automatically at specified
intervals.
</CardDescription>
</div>
<div className="flex items-center gap-2">
{volumeBackups && volumeBackups.length > 0 && (
<>
<HandleVolumeBackups id={id} volumeBackupType={type} />
<div className="flex items-center gap-2">
<RestoreVolumeBackups
id={id}
type={type}
serverId={serverId}
/>
</div>
</>
)}
</div>
</div>
</CardHeader>
<CardContent className="px-0">
{isLoadingVolumeBackups ? (
<div className="flex gap-4 w-full items-center justify-center text-center mx-auto min-h-[45vh]">
<Loader2 className="size-4 text-muted-foreground/70 transition-colors animate-spin self-center" />
<span className="text-sm text-muted-foreground/70">
Loading volume backups...
</span>
</div>
) : volumeBackups && volumeBackups.length > 0 ? (
<div className="grid xl:grid-cols-2 gap-4 grid-cols-1 h-full">
{volumeBackups.map((volumeBackup) => {
const serverId =
volumeBackup.application?.serverId ||
volumeBackup.postgres?.serverId ||
volumeBackup.mysql?.serverId ||
volumeBackup.mariadb?.serverId ||
volumeBackup.mongo?.serverId ||
volumeBackup.redis?.serverId ||
volumeBackup.compose?.serverId;
return (
<div
key={volumeBackup.volumeBackupId}
className="flex items-center 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">
<DatabaseBackup 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">
{volumeBackup.name}
</h3>
<Badge
variant={
volumeBackup.enabled ? "default" : "secondary"
}
className="text-[10px] px-1 py-0"
>
{volumeBackup.enabled ? "Enabled" : "Disabled"}
</Badge>
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Badge
variant="outline"
className="font-mono text-[10px] bg-transparent"
>
Cron: {volumeBackup.cronExpression}
</Badge>
</div>
</div>
</div>
<div className="flex items-center gap-1.5">
<ShowDeploymentsModal
id={volumeBackup.volumeBackupId}
type="volumeBackup"
serverId={serverId || undefined}
>
<Button variant="ghost" size="icon">
<ClipboardList className="size-4 transition-colors " />
</Button>
</ShowDeploymentsModal>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="icon"
isLoading={isLoading}
onClick={async () => {
toast.success("Volume backup run successfully");
await runManually({
volumeBackupId: volumeBackup.volumeBackupId,
})
.then(async () => {
await new Promise((resolve) =>
setTimeout(resolve, 1500),
);
refetchVolumeBackups();
})
.catch(() => {
toast.error("Error running volume backup");
});
}}
>
<Play className="size-4 transition-colors" />
</Button>
</TooltipTrigger>
<TooltipContent>
Run Manual Volume Backup
</TooltipContent>
</Tooltip>
</TooltipProvider>
<HandleVolumeBackups
volumeBackupId={volumeBackup.volumeBackupId}
id={id}
volumeBackupType={type}
/>
<DialogAction
title="Delete Volume Backup"
description="Are you sure you want to delete this volume backup?"
type="destructive"
onClick={async () => {
await deleteVolumeBackup({
volumeBackupId: volumeBackup.volumeBackupId,
})
.then(() => {
utils.volumeBackups.list.invalidate({
id,
volumeBackupType: type,
});
toast.success("Volume backup deleted successfully");
})
.catch(() => {
toast.error("Error deleting volume backup");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10 "
isLoading={isDeleting}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</div>
</div>
);
})}
</div>
) : (
<div className="flex flex-col gap-2 items-center justify-center py-12 rounded-lg">
<DatabaseBackup className="size-8 mb-4 text-muted-foreground" />
<p className="text-lg font-medium text-muted-foreground">
No volume backups
</p>
<p className="text-sm text-muted-foreground mt-1">
Create your first volume backup to automate your workflows
</p>
<div className="flex items-center gap-2">
<HandleVolumeBackups id={id} volumeBackupType={type} />
<RestoreVolumeBackups id={id} type={type} serverId={serverId} />
</div>
</div>
)}
</CardContent>
</Card>
);
};

View File

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

View File

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

View File

@@ -1,3 +1,12 @@
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 { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
@@ -20,15 +29,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, {
@@ -126,7 +126,7 @@ export const DeleteService = ({ id, type }: Props) => {
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>

View File

@@ -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 {

View File

@@ -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 { 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;
@@ -77,7 +76,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
composeId,
});
})
.catch((_e) => {
.catch(() => {
toast.error("Error updating the Compose config");
});
};
@@ -142,9 +141,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"

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