Compare commits

..

114 Commits

Author SHA1 Message Date
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
163 changed files with 7790 additions and 1025 deletions

View File

@@ -2,7 +2,8 @@ name: Build Docker images
on: on:
push: push:
branches: ["canary", "main", "feat/monitoring"] branches: [main, canary]
workflow_dispatch:
jobs: jobs:
build-and-push-cloud-image: build-and-push-cloud-image:

View File

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

View File

@@ -11,12 +11,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Setup biomeJs - name: Setup biomeJs
uses: biomejs/setup-biome@v2 uses: biomejs/setup-biome@v2
- name: Run Biome formatter - 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

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

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

View File

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

View File

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

View File

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

View File

@@ -60,8 +60,6 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
<div> <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.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> <a href="https://www.lxaer.com/?ref=dokploy"><img src=".github/sponsors/lxaer.png" alt="LX Aer" width="100"/></a>
<a href="https://mandarin3d.com/?ref=dokploy"><img src=".github/sponsors/mandarin.png" alt="Mandarin" width="100"/></a>
<a href="https://lightnode.com/?ref=dokploy"><img src=".github/sponsors/light-node.webp" alt="Lightnode" width="300"/></a>
</div> </div>
<!-- Premium Supporters 🥇 --> <!-- Premium Supporters 🥇 -->
@@ -89,25 +87,18 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
### Supporting Members 🥉 ### Supporting Members 🥉
<div> <div>
<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://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> <a href="https://synexa.ai/?ref=dokploy"><img src=".github/sponsors/synexa.png" width="65px" alt="Synexa"/></a>
</div> </div>
### Community Backers 🤝 ### Community Backers 🤝
<div>
<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: #### 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: #### Individuals:

View File

@@ -10,24 +10,28 @@
}, },
"dependencies": { "dependencies": {
"@dokploy/server": "workspace:*", "@dokploy/server": "workspace:*",
"@hono/node-server": "^1.12.1", "@hono/node-server": "^1.14.3",
"@hono/zod-validator": "0.3.0", "@hono/zod-validator": "0.3.0",
"@nerimity/mimiqueue": "1.2.3", "@nerimity/mimiqueue": "1.2.3",
"dotenv": "^16.3.1", "dotenv": "^16.4.5",
"hono": "^4.5.8", "hono": "^4.7.10",
"pino": "9.4.0", "pino": "9.4.0",
"pino-pretty": "11.2.2", "pino-pretty": "11.2.2",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"redis": "4.7.0", "redis": "4.7.0",
"zod": "^3.23.4" "zod": "^3.25.32"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.11.17", "@types/node": "^20.17.51",
"@types/react": "^18.2.37", "@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15", "@types/react-dom": "^18.2.15",
"tsx": "^4.7.1", "tsx": "^4.16.2",
"typescript": "^5.4.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

@@ -29,6 +29,7 @@ const baseApp: ApplicationNested = {
herokuVersion: "", herokuVersion: "",
giteaBranch: "", giteaBranch: "",
giteaBuildPath: "", giteaBuildPath: "",
previewRequireCollaboratorPermissions: false,
giteaId: "", giteaId: "",
giteaOwner: "", giteaOwner: "",
giteaRepository: "", giteaRepository: "",

View File

@@ -18,6 +18,7 @@ const baseApp: ApplicationNested = {
appName: "", appName: "",
autoDeploy: true, autoDeploy: true,
enableSubmodules: false, enableSubmodules: false,
previewRequireCollaboratorPermissions: false,
serverId: "", serverId: "",
branch: null, branch: null,
dockerBuildStage: "", dockerBuildStage: "",

View File

@@ -270,8 +270,8 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
Swarm Settings Swarm Settings
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-5xl p-0"> <DialogContent className="sm:max-w-5xl p-0">
<DialogHeader className="p-6"> <DialogHeader>
<DialogTitle>Swarm Settings</DialogTitle> <DialogTitle>Swarm Settings</DialogTitle>
<DialogDescription> <DialogDescription>
Update certain settings using a json object. Update certain settings using a json object.
@@ -753,7 +753,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 <Button
isLoading={isLoading} isLoading={isLoading}
form="hook-form-add-permissions" form="hook-form-add-permissions"

View File

@@ -185,7 +185,7 @@ export const ShowImport = ({ composeId }: Props) => {
</Button> </Button>
</div> </div>
<Dialog open={showModal} onOpenChange={setShowModal}> <Dialog open={showModal} onOpenChange={setShowModal}>
<DialogContent className="max-h-[80vh] max-w-[50vw] overflow-y-auto"> <DialogContent className="max-w-[50vw]">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-2xl font-bold"> <DialogTitle className="text-2xl font-bold">
Template Information Template Information

View File

@@ -124,7 +124,7 @@ export const HandlePorts = ({
<Button>{children}</Button> <Button>{children}</Button>
)} )}
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle>Ports</DialogTitle> <DialogTitle>Ports</DialogTitle>
<DialogDescription> <DialogDescription>

View File

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

View File

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

View File

@@ -7,6 +7,9 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { LockKeyhole, Trash2 } from "lucide-react"; import { LockKeyhole, Trash2 } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -58,19 +61,18 @@ export const ShowSecurity = ({ applicationId }: Props) => {
<div className="flex flex-col gap-6 "> <div className="flex flex-col gap-6 ">
{data?.security.map((security) => ( {data?.security.map((security) => (
<div key={security.securityId}> <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="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 sm:grid-cols-2 flex-col gap-4 sm:gap-8"> <div className="grid grid-cols-1 md:grid-cols-2 flex-col gap-4 md:gap-8">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-2">
<span className="font-medium">Username</span> <Label>Username</Label>
<span className="text-sm text-muted-foreground"> <Input disabled value={security.username} />
{security.username}
</span>
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-2">
<span className="font-medium">Password</span> <Label>Password</Label>
<span className="text-sm text-muted-foreground"> <ToggleVisibilityInput
{security.password} value={security.password}
</span> disabled
/>
</div> </div>
</div> </div>
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">

View File

@@ -122,7 +122,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
<DialogTrigger asChild> <DialogTrigger asChild>
<Button isLoading={isLoading}>Modify</Button> <Button isLoading={isLoading}>Modify</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-4xl"> <DialogContent className="sm:max-w-4xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Update traefik config</DialogTitle> <DialogTitle>Update traefik config</DialogTitle>
<DialogDescription>Update the traefik config</DialogDescription> <DialogDescription>Update the traefik config</DialogDescription>

View File

@@ -151,7 +151,7 @@ export const AddVolumes = ({
<DialogTrigger className="" asChild> <DialogTrigger className="" asChild>
<Button>{children}</Button> <Button>{children}</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-3xl"> <DialogContent className="sm:max-w-3xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Volumes / Mounts</DialogTitle> <DialogTitle>Volumes / Mounts</DialogTitle>
</DialogHeader> </DialogHeader>

View File

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

View File

@@ -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> <DialogHeader>
<DialogTitle>Deployment</DialogTitle> <DialogTitle>Deployment</DialogTitle>
<DialogDescription className="flex items-center gap-2"> <DialogDescription className="flex items-center gap-2">

View File

@@ -50,7 +50,7 @@ export const ShowDeploymentsModal = ({
</Button> </Button>
)} )}
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl p-0"> <DialogContent className="sm:max-w-5xl p-0">
<ShowDeployments <ShowDeployments
id={id} id={id}
type={type} type={type}

View File

@@ -1,4 +1,5 @@
import { DateTooltip } from "@/components/shared/date-tooltip"; import { DateTooltip } from "@/components/shared/date-tooltip";
import { DialogAction } from "@/components/shared/dialog-action";
import { StatusTooltip } from "@/components/shared/status-tooltip"; import { StatusTooltip } from "@/components/shared/status-tooltip";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -10,14 +11,13 @@ import {
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { type RouterOutputs, api } from "@/utils/api"; import { type RouterOutputs, api } from "@/utils/api";
import { Clock, Loader2, RocketIcon, Settings, RefreshCcw } from "lucide-react"; import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { toast } from "sonner";
import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
import { CancelQueues } from "./cancel-queues"; import { CancelQueues } from "./cancel-queues";
import { RefreshToken } from "./refresh-token"; import { RefreshToken } from "./refresh-token";
import { ShowDeployment } from "./show-deployment"; 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 { interface Props {
id: string; id: string;

View File

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

View File

@@ -292,7 +292,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
<DialogTrigger className="" asChild> <DialogTrigger className="" asChild>
{children} {children}
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl"> <DialogContent className="sm:max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Domain</DialogTitle> <DialogTitle>Domain</DialogTitle>
<DialogDescription>{dictionary.dialogDescription}</DialogDescription> <DialogDescription>{dictionary.dialogDescription}</DialogDescription>

View File

@@ -43,7 +43,7 @@ import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useEffect } from "react"; import { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
@@ -96,6 +96,16 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
const repository = form.watch("repository"); const repository = form.watch("repository");
const gitlabId = form.watch("gitlabId"); 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 { const {
data: repositories, data: repositories,
isLoading: isLoadingRepositories, isLoading: isLoadingRepositories,
@@ -224,7 +234,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
<FormLabel>Repository</FormLabel> <FormLabel>Repository</FormLabel>
{field.value.owner && field.value.repo && ( {field.value.owner && field.value.repo && (
<Link <Link
href={`https://gitlab.com/${field.value.owner}/${field.value.repo}`} href={`${gitlabUrl}/${field.value.owner}/${field.value.repo}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary" className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"

View File

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

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

View File

@@ -46,6 +46,7 @@ const schema = z
previewPath: z.string(), previewPath: z.string(),
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]), previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
previewCustomCertResolver: z.string().optional(), previewCustomCertResolver: z.string().optional(),
previewRequireCollaboratorPermissions: z.boolean(),
}) })
.superRefine((input, ctx) => { .superRefine((input, ctx) => {
if ( if (
@@ -83,6 +84,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewHttps: false, previewHttps: false,
previewPath: "/", previewPath: "/",
previewCertificateType: "none", previewCertificateType: "none",
previewRequireCollaboratorPermissions: true,
}, },
resolver: zodResolver(schema), resolver: zodResolver(schema),
}); });
@@ -105,6 +107,8 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewPath: data.previewPath || "/", previewPath: data.previewPath || "/",
previewCertificateType: data.previewCertificateType || "none", previewCertificateType: data.previewCertificateType || "none",
previewCustomCertResolver: data.previewCustomCertResolver || "", previewCustomCertResolver: data.previewCustomCertResolver || "",
previewRequireCollaboratorPermissions:
data.previewRequireCollaboratorPermissions || true,
}); });
} }
}, [data]); }, [data]);
@@ -121,6 +125,8 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewPath: formData.previewPath, previewPath: formData.previewPath,
previewCertificateType: formData.previewCertificateType, previewCertificateType: formData.previewCertificateType,
previewCustomCertResolver: formData.previewCustomCertResolver, previewCustomCertResolver: formData.previewCustomCertResolver,
previewRequireCollaboratorPermissions:
formData.previewRequireCollaboratorPermissions,
}) })
.then(() => { .then(() => {
toast.success("Preview Deployments settings updated"); toast.success("Preview Deployments settings updated");
@@ -138,7 +144,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
Configure Configure
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl w-full"> <DialogContent className="sm:max-w-5xl w-full">
<DialogHeader> <DialogHeader>
<DialogTitle>Preview Deployment Settings</DialogTitle> <DialogTitle>Preview Deployment Settings</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -312,6 +318,37 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
</div> </div>
</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 <FormField
control={form.control} control={form.control}
name="env" name="env"

View File

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

View File

@@ -91,7 +91,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
return ( return (
<div <div
key={schedule.scheduleId} 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 items-start gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5"> <div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5">

View File

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

View File

@@ -257,7 +257,7 @@ export const HandleVolumeBackups = ({
</DialogTrigger> </DialogTrigger>
<DialogContent <DialogContent
className={cn( className={cn(
"max-h-screen overflow-y-auto", "overflow-y-auto",
volumeBackupType === "compose" || volumeBackupType === "application" volumeBackupType === "compose" || volumeBackupType === "application"
? "max-h-[95vh] sm:max-w-2xl" ? "max-h-[95vh] sm:max-w-2xl"
: " sm:max-w-lg", : " sm:max-w-lg",

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { DrawerLogs } from "@/components/shared/drawer-logs"; import { DrawerLogs } from "@/components/shared/drawer-logs";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -42,9 +43,8 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
import { formatBytes } from "../../database/backups/restore-backup"; import { formatBytes } from "../../database/backups/restore-backup";
import { AlertBlock } from "@/components/shared/alert-block"; import { type LogLine, parseLogs } from "../../docker/logs/utils";
interface Props { interface Props {
id: string; id: string;
@@ -161,7 +161,7 @@ export const RestoreVolumeBackups = ({ id, type, serverId }: Props) => {
Restore Volume Backup Restore Volume Backup
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center"> <DialogTitle className="flex items-center">
<RotateCcw className="mr-2 size-4" /> <RotateCcw className="mr-2 size-4" />

View File

@@ -23,8 +23,8 @@ import {
Trash2, Trash2,
} from "lucide-react"; } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { HandleVolumeBackups } from "./handle-volume-backups";
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal"; import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
import { HandleVolumeBackups } from "./handle-volume-backups";
import { RestoreVolumeBackups } from "./restore-volume-backups"; import { RestoreVolumeBackups } from "./restore-volume-backups";
interface Props { interface Props {
@@ -113,7 +113,7 @@ export const ShowVolumeBackups = ({
return ( return (
<div <div
key={volumeBackup.volumeBackupId} key={volumeBackup.volumeBackupId}
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 items-start gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5"> <div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5">

View File

@@ -126,7 +126,7 @@ export const DeleteService = ({ id, type }: Props) => {
<Trash2 className="size-4 text-primary group-hover:text-red-500" /> <Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle> <DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -1,3 +1,4 @@
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
import { import {
BitbucketIcon, BitbucketIcon,
GitIcon, GitIcon,
@@ -11,6 +12,7 @@ import { api } from "@/utils/api";
import { CodeIcon, GitBranch, Loader2 } from "lucide-react"; import { CodeIcon, GitBranch, Loader2 } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner";
import { ComposeFileEditor } from "../compose-file-editor"; import { ComposeFileEditor } from "../compose-file-editor";
import { ShowConvertedCompose } from "../show-converted-compose"; import { ShowConvertedCompose } from "../show-converted-compose";
import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose"; import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose";
@@ -18,8 +20,6 @@ import { SaveGitProviderCompose } from "./save-git-provider-compose";
import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose"; import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose";
import { SaveGithubProviderCompose } from "./save-github-provider-compose"; import { SaveGithubProviderCompose } from "./save-github-provider-compose";
import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose"; import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose";
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
import { toast } from "sonner";
type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea"; type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea";
interface Props { interface Props {
@@ -142,8 +142,8 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => {
setSab(e as TabState); setSab(e as TabState);
}} }}
> >
<div className="flex flex-row items-center justify-between w-full gap-4"> <div className="flex flex-row items-center justify-between w-full overflow-auto">
<TabsList className="md:grid md:w-fit md:grid-cols-6 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden"> <TabsList className="flex gap-4 justify-start bg-transparent">
<TabsTrigger <TabsTrigger
value="github" 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" 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

@@ -52,7 +52,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
Preview Compose Preview Compose
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-6xl max-h-[50rem] overflow-y-auto"> <DialogContent className="sm:max-w-6xl max-h-[50rem]">
<DialogHeader> <DialogHeader>
<DialogTitle>Converted Compose</DialogTitle> <DialogTitle>Converted Compose</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -23,7 +23,7 @@ export const ShowUtilities = ({ composeId }: Props) => {
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost">Show Utilities</Button> <Button variant="ghost">Show Utilities</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl"> <DialogContent className="sm:max-w-5xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Utilities </DialogTitle> <DialogTitle>Utilities </DialogTitle>
<DialogDescription>Modify the application data</DialogDescription> <DialogDescription>Modify the application data</DialogDescription>

View File

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

View File

@@ -329,7 +329,7 @@ export const HandleBackup = ({
</Button> </Button>
)} )}
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-2xl max-h-screen overflow-y-auto"> <DialogContent className="sm:max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{backupId ? "Update Backup" : "Create Backup"} {backupId ? "Update Backup" : "Create Backup"}

View File

@@ -324,7 +324,7 @@ export const RestoreBackup = ({
Restore Backup Restore Backup
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center"> <DialogTitle className="flex items-center">
<RotateCcw className="mr-2 size-4" /> <RotateCcw className="mr-2 size-4" />

View File

@@ -42,7 +42,7 @@ export const ShowContainerConfig = ({ containerId, serverId }: Props) => {
See in detail the config of this container See in detail the config of this container
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="text-wrap rounded-lg border p-4 text-sm bg-card overflow-y-auto max-h-[80vh]"> <div className="text-wrap rounded-lg border p-4 overflow-y-auto text-sm bg-card max-h-[80vh]">
<code> <code>
<pre className="whitespace-pre-wrap break-words"> <pre className="whitespace-pre-wrap break-words">
<CodeEditor <CodeEditor

View File

@@ -40,7 +40,7 @@ export const ShowDockerModalLogs = ({
{children} {children}
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl"> <DialogContent className="sm:max-w-7xl">
<DialogHeader> <DialogHeader>
<DialogTitle>View Logs</DialogTitle> <DialogTitle>View Logs</DialogTitle>
<DialogDescription>View the logs for {containerId}</DialogDescription> <DialogDescription>View the logs for {containerId}</DialogDescription>

View File

@@ -40,7 +40,7 @@ export const ShowDockerModalStackLogs = ({
{children} {children}
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl"> <DialogContent className="sm:max-w-7xl">
<DialogHeader> <DialogHeader>
<DialogTitle>View Logs</DialogTitle> <DialogTitle>View Logs</DialogTitle>
<DialogDescription>View the logs for {containerId}</DialogDescription> <DialogDescription>View the logs for {containerId}</DialogDescription>

View File

@@ -60,7 +60,7 @@ export const DockerTerminalModal = ({
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent <DialogContent
className="max-h-screen overflow-y-auto sm:max-w-7xl" className="sm:max-w-7xl"
onEscapeKeyDown={(event) => event.preventDefault()} onEscapeKeyDown={(event) => event.preventDefault()}
> >
<DialogHeader> <DialogHeader>

View File

@@ -97,7 +97,7 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" /> <PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle>Modify MariaDB</DialogTitle> <DialogTitle>Modify MariaDB</DialogTitle>
<DialogDescription>Update the MariaDB data</DialogDescription> <DialogDescription>Update the MariaDB data</DialogDescription>

View File

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

View File

@@ -123,7 +123,7 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {
? queryError.message ? queryError.message
: "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."} : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}
</p> </p>
<p className=" text-sm text-muted-foreground">URL: {baseUrl}</p> <p className="text-sm text-muted-foreground">URL: {baseUrl}</p>
</div> </div>
</div> </div>
); );

View File

@@ -143,7 +143,7 @@ export const ShowPaidMonitoring = ({
? queryError.message ? queryError.message
: "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."} : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}
</p> </p>
<p className=" text-sm text-muted-foreground">URL: {BASE_URL}</p> <p className="text-sm text-muted-foreground">URL: {BASE_URL}</p>
</div> </div>
</div> </div>
); );

View File

@@ -97,7 +97,7 @@ export const UpdateMysql = ({ mysqlId }: Props) => {
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" /> <PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle>Modify MySQL</DialogTitle> <DialogTitle>Modify MySQL</DialogTitle>
<DialogDescription>Update the MySQL data</DialogDescription> <DialogDescription>Update the MySQL data</DialogDescription>

View File

@@ -155,7 +155,7 @@ export function AddOrganization({ organizationId }: Props) {
control={form.control} control={form.control}
name="logo" name="logo"
render={({ field }) => ( render={({ field }) => (
<FormItem className=" gap-4"> <FormItem className="gap-4">
<FormLabel className="text-right">Logo URL</FormLabel> <FormLabel className="text-right">Logo URL</FormLabel>
<FormControl> <FormControl>
<Input <Input
@@ -169,7 +169,7 @@ export function AddOrganization({ organizationId }: Props) {
</FormItem> </FormItem>
)} )}
/> />
<DialogFooter className="mt-4"> <DialogFooter>
<Button type="submit" isLoading={isLoading}> <Button type="submit" isLoading={isLoading}>
{organizationId ? "Update organization" : "Create organization"} {organizationId ? "Update organization" : "Create organization"}
</Button> </Button>

View File

@@ -99,7 +99,7 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
<PenBox className="size-3.5 text-primary group-hover:text-blue-500" /> <PenBox className="size-3.5 text-primary group-hover:text-blue-500" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle>Modify Postgres</DialogTitle> <DialogTitle>Modify Postgres</DialogTitle>
<DialogDescription>Update the Postgres data</DialogDescription> <DialogDescription>Update the Postgres data</DialogDescription>

View File

@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Folder, HelpCircle } 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 { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -37,12 +43,6 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { slugify } from "@/lib/slug"; import { slugify } from "@/lib/slug";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Folder, HelpCircle } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const AddTemplateSchema = z.object({ const AddTemplateSchema = z.object({
name: z.string().min(1, { name: z.string().min(1, {
@@ -75,6 +75,8 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
const slug = slugify(projectName); const slug = slugify(projectName);
const { data: servers } = api.server.withSSHKey.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery();
const hasServers = servers && servers.length > 0;
const { mutateAsync, isLoading, error, isError } = const { mutateAsync, isLoading, error, isError } =
api.application.create.useMutation(); api.application.create.useMutation();
@@ -119,7 +121,7 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
<span>Application</span> <span>Application</span>
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle>Create</DialogTitle> <DialogTitle>Create</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -155,68 +157,84 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
</FormItem> </FormItem>
)} )}
/> />
<FormField {hasServers && (
control={form.control} <FormField
name="serverId" control={form.control}
render={({ field }) => ( name="serverId"
<FormItem> render={({ field }) => (
<TooltipProvider delayDuration={0}> <FormItem>
<Tooltip> <TooltipProvider delayDuration={0}>
<TooltipTrigger asChild> <Tooltip>
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center"> <TooltipTrigger asChild>
Select a Server {!isCloud ? "(Optional)" : ""} <FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
<HelpCircle className="size-4 text-muted-foreground" /> Select a Server {!isCloud ? "(Optional)" : ""}
</FormLabel> <HelpCircle className="size-4 text-muted-foreground" />
</TooltipTrigger> </FormLabel>
<TooltipContent </TooltipTrigger>
className="z-[999] w-[300px]" <TooltipContent
align="start" className="z-[999] w-[300px]"
side="top" align="start"
> side="top"
<span> >
If no server is selected, the application will be <span>
deployed on the server where the user is logged in. If no server is selected, the application will be
</span> deployed on the server where the user is logged in.
</TooltipContent> </span>
</Tooltip> </TooltipContent>
</TooltipProvider> </Tooltip>
</TooltipProvider>
<Select <Select
onValueChange={field.onChange} onValueChange={field.onChange}
defaultValue={field.value} defaultValue={field.value}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a Server" /> <SelectValue placeholder="Select a Server" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{servers?.map((server) => ( {servers?.map((server) => (
<SelectItem <SelectItem
key={server.serverId} key={server.serverId}
value={server.serverId} value={server.serverId}
> >
<span className="flex items-center gap-2 justify-between w-full"> <span className="flex items-center gap-2 justify-between w-full">
<span>{server.name}</span> <span>{server.name}</span>
<span className="text-muted-foreground text-xs self-center"> <span className="text-muted-foreground text-xs self-center">
{server.ipAddress} {server.ipAddress}
</span>
</span> </span>
</span> </SelectItem>
</SelectItem> ))}
))} <SelectLabel>Servers ({servers?.length})</SelectLabel>
<SelectLabel>Servers ({servers?.length})</SelectLabel> </SelectGroup>
</SelectGroup> </SelectContent>
</SelectContent> </Select>
</Select> <FormMessage />
<FormMessage /> </FormItem>
</FormItem> )}
)} />
/> )}
<FormField <FormField
control={form.control} control={form.control}
name="appName" name="appName"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>App Name</FormLabel> <FormLabel className="flex items-center gap-2">
App Name
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent side="right">
<p>
This will be the name of the Docker Swarm service
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</FormLabel>
<FormControl> <FormControl>
<Input placeholder="my-app" {...field} /> <Input placeholder="my-app" {...field} />
</FormControl> </FormControl>

View File

@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { CircuitBoard, HelpCircle } 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 { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -37,12 +43,6 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { slugify } from "@/lib/slug"; import { slugify } from "@/lib/slug";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CircuitBoard, HelpCircle } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const AddComposeSchema = z.object({ const AddComposeSchema = z.object({
composeType: z.enum(["docker-compose", "stack"]).optional(), composeType: z.enum(["docker-compose", "stack"]).optional(),
@@ -78,6 +78,8 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
const { mutateAsync, isLoading, error, isError } = const { mutateAsync, isLoading, error, isError } =
api.compose.create.useMutation(); api.compose.create.useMutation();
const hasServers = servers && servers.length > 0;
const form = useForm<AddCompose>({ const form = useForm<AddCompose>({
defaultValues: { defaultValues: {
name: "", name: "",
@@ -124,7 +126,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
<span>Compose</span> <span>Compose</span>
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-xl"> <DialogContent className="sm:max-w-xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Create Compose</DialogTitle> <DialogTitle>Create Compose</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -163,62 +165,64 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
)} )}
/> />
</div> </div>
<FormField {hasServers && (
control={form.control} <FormField
name="serverId" control={form.control}
render={({ field }) => ( name="serverId"
<FormItem> render={({ field }) => (
<TooltipProvider delayDuration={0}> <FormItem>
<Tooltip> <TooltipProvider delayDuration={0}>
<TooltipTrigger asChild> <Tooltip>
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center"> <TooltipTrigger asChild>
Select a Server {!isCloud ? "(Optional)" : ""} <FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
<HelpCircle className="size-4 text-muted-foreground" /> Select a Server {!isCloud ? "(Optional)" : ""}
</FormLabel> <HelpCircle className="size-4 text-muted-foreground" />
</TooltipTrigger> </FormLabel>
<TooltipContent </TooltipTrigger>
className="z-[999] w-[300px]" <TooltipContent
align="start" className="z-[999] w-[300px]"
side="top" align="start"
> side="top"
<span> >
If no server is selected, the application will be <span>
deployed on the server where the user is logged in. If no server is selected, the application will be
</span> deployed on the server where the user is logged in.
</TooltipContent> </span>
</Tooltip> </TooltipContent>
</TooltipProvider> </Tooltip>
</TooltipProvider>
<Select <Select
onValueChange={field.onChange} onValueChange={field.onChange}
defaultValue={field.value} defaultValue={field.value}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a Server" /> <SelectValue placeholder="Select a Server" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{servers?.map((server) => ( {servers?.map((server) => (
<SelectItem <SelectItem
key={server.serverId} key={server.serverId}
value={server.serverId} value={server.serverId}
> >
<span className="flex items-center gap-2 justify-between w-full"> <span className="flex items-center gap-2 justify-between w-full">
<span>{server.name}</span> <span>{server.name}</span>
<span className="text-muted-foreground text-xs self-center"> <span className="text-muted-foreground text-xs self-center">
{server.ipAddress} {server.ipAddress}
</span>
</span> </span>
</span> </SelectItem>
</SelectItem> ))}
))} <SelectLabel>Servers ({servers?.length})</SelectLabel>
<SelectLabel>Servers ({servers?.length})</SelectLabel> </SelectGroup>
</SelectGroup> </SelectContent>
</SelectContent> </Select>
</Select> <FormMessage />
<FormMessage /> </FormItem>
</FormItem> )}
)} />
/> )}
<FormField <FormField
control={form.control} control={form.control}
name="appName" name="appName"

View File

@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Database, HelpCircle } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { import {
MariadbIcon, MariadbIcon,
MongodbIcon, MongodbIcon,
@@ -37,14 +43,14 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { slugify } from "@/lib/slug"; import { slugify } from "@/lib/slug";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Database } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
type DbType = typeof mySchema._type.type; type DbType = typeof mySchema._type.type;
@@ -163,6 +169,8 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
const mariadbMutation = api.mariadb.create.useMutation(); const mariadbMutation = api.mariadb.create.useMutation();
const mysqlMutation = api.mysql.create.useMutation(); const mysqlMutation = api.mysql.create.useMutation();
const hasServers = servers && servers.length > 0;
const form = useForm<AddDatabase>({ const form = useForm<AddDatabase>({
defaultValues: { defaultValues: {
type: "postgres", type: "postgres",
@@ -283,7 +291,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
<span>Database</span> <span>Database</span>
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen md:max-h-[90vh] overflow-y-auto sm:max-w-2xl"> <DialogContent className="md:max-h-[90vh] sm:max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Databases</DialogTitle> <DialogTitle>Databases</DialogTitle>
</DialogHeader> </DialogHeader>
@@ -374,45 +382,62 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
</FormItem> </FormItem>
)} )}
/> />
<FormField {hasServers && (
control={form.control} <FormField
name="serverId" control={form.control}
render={({ field }) => ( name="serverId"
<FormItem> render={({ field }) => (
<FormLabel>Select a Server</FormLabel> <FormItem>
<Select <FormLabel>Select a Server</FormLabel>
onValueChange={field.onChange} <Select
defaultValue={field.value || ""} onValueChange={field.onChange}
> defaultValue={field.value || ""}
<SelectTrigger> >
<SelectValue placeholder="Select a Server" /> <SelectTrigger>
</SelectTrigger> <SelectValue placeholder="Select a Server" />
<SelectContent> </SelectTrigger>
<SelectGroup> <SelectContent>
{servers?.map((server) => ( <SelectGroup>
<SelectItem {servers?.map((server) => (
key={server.serverId} <SelectItem
value={server.serverId} key={server.serverId}
> value={server.serverId}
{server.name} >
</SelectItem> {server.name}
))} </SelectItem>
<SelectLabel> ))}
Servers ({servers?.length}) <SelectLabel>
</SelectLabel> Servers ({servers?.length})
</SelectGroup> </SelectLabel>
</SelectContent> </SelectGroup>
</Select> </SelectContent>
<FormMessage /> </Select>
</FormItem> <FormMessage />
)} </FormItem>
/> )}
/>
)}
<FormField <FormField
control={form.control} control={form.control}
name="appName" name="appName"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>App Name</FormLabel> <FormLabel className="flex items-center gap-2">
App Name
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent side="right">
<p>
This will be the name of the Docker Swarm
service
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</FormLabel>
<FormControl> <FormControl>
<Input placeholder="my-app" {...field} /> <Input placeholder="my-app" {...field} />
</FormControl> </FormControl>

View File

@@ -1,3 +1,18 @@
import {
BookText,
CheckIcon,
ChevronsUpDown,
Globe,
HelpCircle,
LayoutGrid,
List,
Loader2,
PuzzleIcon,
SearchIcon,
} from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { GithubIcon } from "@/components/icons/data-tools-icons"; import { GithubIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { import {
@@ -54,21 +69,6 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import {
BookText,
CheckIcon,
ChevronsUpDown,
Globe,
HelpCircle,
LayoutGrid,
List,
Loader2,
PuzzleIcon,
SearchIcon,
} from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { toast } from "sonner";
const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url"; const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url";
@@ -137,6 +137,8 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => {
return matchesTags && matchesQuery; return matchesTags && matchesQuery;
}) || []; }) || [];
const hasServers = servers && servers.length > 0;
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger className="w-full"> <DialogTrigger className="w-full">
@@ -148,7 +150,7 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => {
<span>Template</span> <span>Template</span>
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen sm:max-w-[90vw] p-0"> <DialogContent className="sm:max-w-[90vw] p-0">
<DialogHeader className="sticky top-0 z-10 bg-background p-6 border-b"> <DialogHeader className="sticky top-0 z-10 bg-background p-6 border-b">
<div className="flex flex-col space-y-6"> <div className="flex flex-col space-y-6">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6"> <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6">
@@ -425,60 +427,62 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => {
project. project.
</AlertDialogDescription> </AlertDialogDescription>
<div> {hasServers && (
<TooltipProvider delayDuration={0}> <div>
<Tooltip> <TooltipProvider delayDuration={0}>
<TooltipTrigger asChild> <Tooltip>
<Label className="break-all w-fit flex flex-row gap-1 items-center pb-2 pt-3.5"> <TooltipTrigger asChild>
Select a Server{" "} <Label className="break-all w-fit flex flex-row gap-1 items-center pb-2 pt-3.5">
{!isCloud ? "(Optional)" : ""} Select a Server{" "}
<HelpCircle className="size-4 text-muted-foreground" /> {!isCloud ? "(Optional)" : ""}
</Label> <HelpCircle className="size-4 text-muted-foreground" />
</TooltipTrigger> </Label>
<TooltipContent </TooltipTrigger>
className="z-[999] w-[300px]" <TooltipContent
align="start" className="z-[999] w-[300px]"
side="top" align="start"
> side="top"
<span> >
If no server is selected, the application <span>
will be deployed on the server where the If no server is selected, the
user is logged in. application will be deployed on the
</span> server where the user is logged in.
</TooltipContent> </span>
</Tooltip> </TooltipContent>
</TooltipProvider> </Tooltip>
</TooltipProvider>
<Select <Select
onValueChange={(e) => { onValueChange={(e) => {
setServerId(e); setServerId(e);
}} }}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a Server" /> <SelectValue placeholder="Select a Server" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{servers?.map((server) => ( {servers?.map((server) => (
<SelectItem <SelectItem
key={server.serverId} key={server.serverId}
value={server.serverId} value={server.serverId}
> >
<span className="flex items-center gap-2 justify-between w-full"> <span className="flex items-center gap-2 justify-between w-full">
<span>{server.name}</span> <span>{server.name}</span>
<span className="text-muted-foreground text-xs self-center"> <span className="text-muted-foreground text-xs self-center">
{server.ipAddress} {server.ipAddress}
</span>
</span> </span>
</span> </SelectItem>
</SelectItem> ))}
))} <SelectLabel>
<SelectLabel> Servers ({servers?.length})
Servers ({servers?.length}) </SelectLabel>
</SelectLabel> </SelectGroup>
</SelectGroup> </SelectContent>
</SelectContent> </Select>
</Select> </div>
</div> )}
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>Cancel</AlertDialogCancel>

View File

@@ -25,6 +25,7 @@ const examples = [
export const StepOne = ({ setTemplateInfo, templateInfo }: any) => { export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
// Get servers from the API // Get servers from the API
const { data: servers } = api.server.withSSHKey.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery();
const hasServers = servers && servers.length > 0;
const handleExampleClick = (example: string) => { const handleExampleClick = (example: string) => {
setTemplateInfo({ ...templateInfo, userInput: example }); setTemplateInfo({ ...templateInfo, userInput: example });
@@ -47,37 +48,39 @@ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
/> />
</div> </div>
<div className="space-y-2"> {hasServers && (
<Label htmlFor="server-deploy"> <div className="space-y-2">
Select the server where you want to deploy (optional) <Label htmlFor="server-deploy">
</Label> Select the server where you want to deploy (optional)
<Select </Label>
value={templateInfo.server?.serverId} <Select
onValueChange={(value) => { value={templateInfo.server?.serverId}
const server = servers?.find((s) => s.serverId === value); onValueChange={(value) => {
if (server) { const server = servers?.find((s) => s.serverId === value);
setTemplateInfo({ if (server) {
...templateInfo, setTemplateInfo({
server: server, ...templateInfo,
}); server: server,
} });
}} }
> }}
<SelectTrigger className="w-full"> >
<SelectValue placeholder="Select a server" /> <SelectTrigger className="w-full">
</SelectTrigger> <SelectValue placeholder="Select a server" />
<SelectContent> </SelectTrigger>
<SelectGroup> <SelectContent>
{servers?.map((server) => ( <SelectGroup>
<SelectItem key={server.serverId} value={server.serverId}> {servers?.map((server) => (
{server.name} <SelectItem key={server.serverId} value={server.serverId}>
</SelectItem> {server.name}
))} </SelectItem>
<SelectLabel>Servers ({servers?.length})</SelectLabel> ))}
</SelectGroup> <SelectLabel>Servers ({servers?.length})</SelectLabel>
</SelectContent> </SelectGroup>
</Select> </SelectContent>
</div> </Select>
</div>
)}
<div className="space-y-2"> <div className="space-y-2">
<Label>Examples:</Label> <Label>Examples:</Label>

View File

@@ -199,7 +199,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Generating template suggestions based on your input... Generating template suggestions based on your input...
</p> </p>
<pre>{templateInfo.userInput}</pre> <pre className="whitespace-normal">{templateInfo.userInput}</pre>
</div> </div>
); );
} }
@@ -259,7 +259,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
<AccordionItem value="description"> <AccordionItem value="description">
<AccordionTrigger>Description</AccordionTrigger> <AccordionTrigger>Description</AccordionTrigger>
<AccordionContent> <AccordionContent>
<ScrollArea className=" w-full rounded-md border p-4"> <ScrollArea className="w-full rounded-md border p-4">
<ReactMarkdown className="text-muted-foreground text-sm"> <ReactMarkdown className="text-muted-foreground text-sm">
{selectedVariant?.description} {selectedVariant?.description}
</ReactMarkdown> </ReactMarkdown>
@@ -289,7 +289,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
<AccordionItem value="env-variables"> <AccordionItem value="env-variables">
<AccordionTrigger>Environment Variables</AccordionTrigger> <AccordionTrigger>Environment Variables</AccordionTrigger>
<AccordionContent> <AccordionContent>
<ScrollArea className=" w-full rounded-md border"> <ScrollArea className="w-full rounded-md border">
<div className="p-4 space-y-4"> <div className="p-4 space-y-4">
{selectedVariant?.envVariables.map((env, index) => ( {selectedVariant?.envVariables.map((env, index) => (
<div <div
@@ -364,7 +364,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
<AccordionItem value="domains"> <AccordionItem value="domains">
<AccordionTrigger>Domains</AccordionTrigger> <AccordionTrigger>Domains</AccordionTrigger>
<AccordionContent> <AccordionContent>
<ScrollArea className=" w-full rounded-md border"> <ScrollArea className="w-full rounded-md border">
<div className="p-4 space-y-4"> <div className="p-4 space-y-4">
{selectedVariant?.domains.map((domain, index) => ( {selectedVariant?.domains.map((domain, index) => (
<div <div

View File

@@ -158,7 +158,7 @@ export const TemplateGenerator = ({ projectId }: Props) => {
<span>AI Assistant</span> <span>AI Assistant</span>
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-4xl w-full flex flex-col"> <DialogContent className="sm:max-w-4xl w-full flex flex-col">
<DialogHeader> <DialogHeader>
<DialogTitle>AI Assistant</DialogTitle> <DialogTitle>AI Assistant</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -94,7 +94,7 @@ export const ProjectEnvironment = ({ projectId, children }: Props) => {
</DropdownMenuItem> </DropdownMenuItem>
)} )}
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-6xl"> <DialogContent className="sm:max-w-6xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Project Environment</DialogTitle> <DialogTitle>Project Environment</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -97,7 +97,7 @@ export const UpdateRedis = ({ redisId }: Props) => {
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" /> <PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle>Modify Redis</DialogTitle> <DialogTitle>Modify Redis</DialogTitle>
<DialogDescription>Update the redis data</DialogDescription> <DialogDescription>Update the redis data</DialogDescription>

View File

@@ -47,7 +47,7 @@ export const columns: ColumnDef<LogEntry>[] = [
cell: ({ row }) => { cell: ({ row }) => {
const log = row.original; const log = row.original;
return ( return (
<div className=" flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center flex-row gap-3 "> <div className="flex items-center flex-row gap-3 ">
{log.RequestMethod}{" "} {log.RequestMethod}{" "}
<div className="inline-flex items-center gap-2 bg-muted px-1.5 py-1 rounded-lg"> <div className="inline-flex items-center gap-2 bg-muted px-1.5 py-1 rounded-lg">
@@ -86,7 +86,7 @@ export const columns: ColumnDef<LogEntry>[] = [
cell: ({ row }) => { cell: ({ row }) => {
const log = row.original; const log = row.original;
return ( return (
<div className=" flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex flex-row gap-3 w-full"> <div className="flex flex-row gap-3 w-full">
{format(new Date(log.StartUTC), "yyyy-MM-dd HH:mm:ss")} {format(new Date(log.StartUTC), "yyyy-MM-dd HH:mm:ss")}
</div> </div>

View File

@@ -142,7 +142,7 @@ export const AddApiKey = () => {
<DialogTrigger asChild> <DialogTrigger asChild>
<Button>Generate New Key</Button> <Button>Generate New Key</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-xl max-h-[90vh] overflow-y-auto"> <DialogContent className="sm:max-w-xl max-h-[90vh]">
<DialogHeader> <DialogHeader>
<DialogTitle>Generate API Key</DialogTitle> <DialogTitle>Generate API Key</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -171,7 +171,7 @@ export const ShowBilling = () => {
)} )}
{isAnnual ? ( {isAnnual ? (
<div className="flex flex-row gap-2 items-center"> <div className="flex flex-row gap-2 items-center">
<p className=" text-2xl font-semibold tracking-tight text-primary "> <p className="text-2xl font-semibold tracking-tight text-primary ">
${" "} ${" "}
{calculatePrice( {calculatePrice(
serverQuantity, serverQuantity,
@@ -180,7 +180,7 @@ export const ShowBilling = () => {
USD USD
</p> </p>
| |
<p className=" text-base font-semibold tracking-tight text-muted-foreground"> <p className="text-base font-semibold tracking-tight text-muted-foreground">
${" "} ${" "}
{( {(
calculatePrice(serverQuantity, isAnnual) / 12 calculatePrice(serverQuantity, isAnnual) / 12
@@ -189,7 +189,7 @@ export const ShowBilling = () => {
</p> </p>
</div> </div>
) : ( ) : (
<p className=" text-2xl font-semibold tracking-tight text-primary "> <p className="text-2xl font-semibold tracking-tight text-primary ">
${" "} ${" "}
{calculatePrice(serverQuantity, isAnnual).toFixed( {calculatePrice(serverQuantity, isAnnual).toFixed(
2, 2,

View File

@@ -41,7 +41,7 @@ export const ShowWelcomeDokploy = () => {
return ( return (
<> <>
<Dialog open={open} onOpenChange={handleClose}> <Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-xl max-h-screen overflow-y-auto"> <DialogContent className="sm:max-w-xl">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-2xl font-semibold text-center"> <DialogTitle className="text-2xl font-semibold text-center">
Welcome to Dokploy Cloud 🎉 Welcome to Dokploy Cloud 🎉

View File

@@ -106,7 +106,7 @@ export const AddCertificate = () => {
Add Certificate Add Certificate
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl"> <DialogContent className="sm:max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Add New Certificate</DialogTitle> <DialogTitle>Add New Certificate</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -222,7 +222,7 @@ export const AddCertificate = () => {
/> />
</form> </form>
<DialogFooter className="flex w-full flex-row !justify-end pt-3"> <DialogFooter className="flex w-full flex-row !justify-end">
<Button <Button
isLoading={isLoading} isLoading={isLoading}
form="hook-form-add-certificate" form="hook-form-add-certificate"

View File

@@ -27,7 +27,7 @@ export const AddNode = ({ serverId }: Props) => {
Add Node Add Node
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-4xl"> <DialogContent className="sm:max-w-4xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Add Node</DialogTitle> <DialogTitle>Add Node</DialogTitle>
<DialogDescription className="flex flex-col gap-2"> <DialogDescription className="flex flex-col gap-2">

View File

@@ -24,7 +24,7 @@ export const ShowNodeData = ({ data }: Props) => {
View Config View Config
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}> <DialogContent className={"sm:max-w-5xl"}>
<DialogHeader> <DialogHeader>
<DialogTitle>Node Config</DialogTitle> <DialogTitle>Node Config</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -20,7 +20,7 @@ export const ShowNodesModal = ({ serverId }: Props) => {
Show Swarm Nodes Show Swarm Nodes
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="min-w-[70vw] overflow-y-auto max-h-screen"> <DialogContent className="min-w-[70vw]">
<div className="grid w-full gap-1"> <div className="grid w-full gap-1">
<ShowNodes serverId={serverId} /> <ShowNodes serverId={serverId} />
</div> </div>

View File

@@ -161,7 +161,7 @@ export const HandleRegistry = ({ registryId }: Props) => {
</Button> </Button>
)} )}
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-2xl max-h-screen overflow-y-auto"> <DialogContent className="sm:max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Add a external registry</DialogTitle> <DialogTitle>Add a external registry</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -316,7 +316,7 @@ export const HandleRegistry = ({ registryId }: Props) => {
/> />
</div> </div>
<DialogFooter className="flex flex-col w-full sm:justify-between gap-4 flex-wrap sm:flex-col col-span-2 mt-6"> <DialogFooter className="flex flex-col w-full sm:justify-between gap-4 flex-wrap sm:flex-col col-span-2">
<div className="flex flex-row gap-2 justify-between"> <div className="flex flex-row gap-2 justify-between">
<Button <Button
type="button" type="button"

View File

@@ -70,6 +70,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
}, },
{ {
enabled: !!destinationId, enabled: !!destinationId,
refetchOnWindowFocus: false,
}, },
); );
const { const {
@@ -204,7 +205,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
</Button> </Button>
)} )}
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl"> <DialogContent className="sm:max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{destinationId ? "Update" : "Add"} Destination {destinationId ? "Update" : "Add"} Destination
@@ -359,7 +360,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
<DialogFooter <DialogFooter
className={cn( className={cn(
isCloud ? "!flex-col" : "flex-row", isCloud ? "!flex-col" : "flex-row",
"flex w-full !justify-between pt-3 gap-4", "flex w-full !justify-between gap-4",
)} )}
> >
{isCloud ? ( {isCloud ? (

View File

@@ -97,7 +97,7 @@ export const AddBitbucketProvider = () => {
<span>Bitbucket</span> <span>Bitbucket</span>
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen"> <DialogContent className="sm:max-w-2xl ">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
Bitbucket Provider <BitbucketIcon className="size-5" /> Bitbucket Provider <BitbucketIcon className="size-5" />

View File

@@ -105,7 +105,7 @@ export const EditBitbucketProvider = ({ bitbucketId }: Props) => {
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" /> <PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen"> <DialogContent className="sm:max-w-2xl ">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
Update Bitbucket <BitbucketIcon className="size-5" /> Update Bitbucket <BitbucketIcon className="size-5" />

View File

@@ -143,7 +143,7 @@ export const AddGiteaProvider = () => {
<span>Gitea</span> <span>Gitea</span>
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen"> <DialogContent className="sm:max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
Gitea Provider <GiteaIcon className="size-5" /> Gitea Provider <GiteaIcon className="size-5" />

View File

@@ -92,7 +92,7 @@ export const EditGithubProvider = ({ githubId }: Props) => {
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" /> <PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen"> <DialogContent className="sm:max-w-2xl ">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
Update Github <GithubIcon className="size-5" /> Update Github <GithubIcon className="size-5" />

View File

@@ -115,7 +115,7 @@ export const AddGitlabProvider = () => {
<span>GitLab</span> <span>GitLab</span>
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen "> <DialogContent className="sm:max-w-2xl ">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
GitLab Provider <GitlabIcon className="size-5" /> GitLab Provider <GitlabIcon className="size-5" />

View File

@@ -105,7 +105,7 @@ export const EditGitlabProvider = ({ gitlabId }: Props) => {
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" /> <PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen"> <DialogContent className="sm:max-w-2xl ">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
Update GitLab <GitlabIcon className="size-5" /> Update GitLab <GitlabIcon className="size-5" />

View File

@@ -33,6 +33,7 @@ import { AddGithubProvider } from "./github/add-github-provider";
import { EditGithubProvider } from "./github/edit-github-provider"; import { EditGithubProvider } from "./github/edit-github-provider";
import { AddGitlabProvider } from "./gitlab/add-gitlab-provider"; import { AddGitlabProvider } from "./gitlab/add-gitlab-provider";
import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider"; import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider";
import { Badge } from "@/components/ui/badge";
export const ShowGitProviders = () => { export const ShowGitProviders = () => {
const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery(); const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery();
@@ -53,7 +54,7 @@ export const ShowGitProviders = () => {
return ( return (
<div className="w-full"> <div className="w-full">
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto"> <Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
<div className="rounded-xl bg-background shadow-md "> <div className="rounded-xl bg-background shadow-md ">
<CardHeader className=""> <CardHeader className="">
<CardTitle className="text-xl flex flex-row gap-2"> <CardTitle className="text-xl flex flex-row gap-2">
@@ -73,14 +74,14 @@ export const ShowGitProviders = () => {
) : ( ) : (
<> <>
{data?.length === 0 ? ( {data?.length === 0 ? (
<div className="flex flex-col items-center gap-3 min-h-[25vh] justify-center"> <div className="flex flex-col items-center gap-3 min-h-[25vh] justify-center">
<GitBranch className="size-8 self-center text-muted-foreground" /> <GitBranch className="size-8 self-center text-muted-foreground" />
<span className="text-base text-muted-foreground text-center"> <span className="text-base text-muted-foreground text-center">
Create your first Git Provider Create your first Git Provider
</span> </span>
<div> <div>
<div className="flex items-center bg-sidebar p-1 w-full rounded-lg"> <div className="flex items-center bg-sidebar p-1 w-full rounded-lg">
<div className="flex items-center gap-4 p-3.5 rounded-lg bg-background border w-full"> <div className="flex flex-wrap items-center gap-4 p-3.5 rounded-lg bg-background border w-full [&>button]:grow">
<AddGithubProvider /> <AddGithubProvider />
<AddGitlabProvider /> <AddGitlabProvider />
<AddBitbucketProvider /> <AddBitbucketProvider />
@@ -90,13 +91,13 @@ export const ShowGitProviders = () => {
</div> </div>
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-4 min-h-[25vh]"> <div className="flex flex-col gap-4 min-h-[25vh]">
<div className="flex flex-col gap-2 rounded-lg "> <div className="flex flex-col gap-2 rounded-lg ">
<span className="text-base font-medium"> <span className="text-base font-medium">
Available Providers Available Providers
</span> </span>
<div className="flex items-center bg-sidebar p-1 w-full rounded-lg"> <div className="flex items-center bg-sidebar p-1 w-full rounded-lg">
<div className="flex items-center gap-4 p-3.5 rounded-lg bg-background border w-full"> <div className="flex flex-wrap items-center gap-4 p-3.5 rounded-lg bg-background border w-full [&>button]:grow">
<AddGithubProvider /> <AddGithubProvider />
<AddGitlabProvider /> <AddGitlabProvider />
<AddBitbucketProvider /> <AddBitbucketProvider />
@@ -158,7 +159,13 @@ export const ShowGitProviders = () => {
<div className="flex flex-row gap-1"> <div className="flex flex-row gap-1">
{!haveGithubRequirements && isGithub && ( {!haveGithubRequirements && isGithub && (
<div className="flex flex-col gap-1"> <div className="flex flex-row gap-1 items-center">
<Badge
variant="outline"
className="text-xs"
>
Action Required
</Badge>
<Link <Link
href={`${gitProvider?.github?.githubAppName}/installations/new?state=gh_setup:${gitProvider?.github.githubId}`} href={`${gitProvider?.github?.githubAppName}/installations/new?state=gh_setup:${gitProvider?.github.githubId}`}
className={buttonVariants({ className={buttonVariants({
@@ -171,7 +178,7 @@ export const ShowGitProviders = () => {
</div> </div>
)} )}
{haveGithubRequirements && isGithub && ( {haveGithubRequirements && isGithub && (
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<Link <Link
href={`${gitProvider?.github?.githubAppName}`} href={`${gitProvider?.github?.githubAppName}`}
target="_blank" target="_blank"
@@ -185,7 +192,13 @@ export const ShowGitProviders = () => {
</div> </div>
)} )}
{!haveGitlabRequirements && isGitlab && ( {!haveGitlabRequirements && isGitlab && (
<div className="flex flex-col gap-1"> <div className="flex flex-row gap-1 items-center">
<Badge
variant="outline"
className="text-xs"
>
Action Required
</Badge>
<Link <Link
href={getGitlabUrl( href={getGitlabUrl(
gitProvider.gitlab?.applicationId || "", gitProvider.gitlab?.applicationId || "",

View File

@@ -408,7 +408,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
</Button> </Button>
)} )}
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-3xl"> <DialogContent className="sm:max-w-3xl">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{notificationId ? "Update" : "Add"} Notification {notificationId ? "Update" : "Add"} Notification
@@ -907,7 +907,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
control={form.control} control={form.control}
name="appBuildError" name="appBuildError"
render={({ field }) => ( render={({ field }) => (
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2"> <FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
<div className="space-y-0.5"> <div className="space-y-0.5">
<FormLabel>App Build Error</FormLabel> <FormLabel>App Build Error</FormLabel>
<FormDescription> <FormDescription>
@@ -928,7 +928,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
control={form.control} control={form.control}
name="databaseBackup" name="databaseBackup"
render={({ field }) => ( render={({ field }) => (
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2"> <FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
<div className="space-y-0.5"> <div className="space-y-0.5">
<FormLabel>Database Backup</FormLabel> <FormLabel>Database Backup</FormLabel>
<FormDescription> <FormDescription>
@@ -949,7 +949,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
control={form.control} control={form.control}
name="dockerCleanup" name="dockerCleanup"
render={({ field }) => ( render={({ field }) => (
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2"> <FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
<div className="space-y-0.5"> <div className="space-y-0.5">
<FormLabel>Docker Cleanup</FormLabel> <FormLabel>Docker Cleanup</FormLabel>
<FormDescription> <FormDescription>
@@ -972,7 +972,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
control={form.control} control={form.control}
name="dokployRestart" name="dokployRestart"
render={({ field }) => ( render={({ field }) => (
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2"> <FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
<div className="space-y-0.5"> <div className="space-y-0.5">
<FormLabel>Dokploy Restart</FormLabel> <FormLabel>Dokploy Restart</FormLabel>
<FormDescription> <FormDescription>
@@ -995,7 +995,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
control={form.control} control={form.control}
name="serverThreshold" name="serverThreshold"
render={({ field }) => ( render={({ field }) => (
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2"> <FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
<div className="space-y-0.5"> <div className="space-y-0.5">
<FormLabel>Server Threshold</FormLabel> <FormLabel>Server Threshold</FormLabel>
<FormDescription> <FormDescription>

View File

@@ -186,7 +186,7 @@ export const Enable2FA = () => {
Enable 2FA Enable 2FA
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-xl"> <DialogContent className="sm:max-w-xl">
<DialogHeader> <DialogHeader>
<DialogTitle>2FA Setup</DialogTitle> <DialogTitle>2FA Setup</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -36,6 +36,7 @@ const profileSchema = z.object({
password: z.string().nullable(), password: z.string().nullable(),
currentPassword: z.string().nullable(), currentPassword: z.string().nullable(),
image: z.string().optional(), image: z.string().optional(),
name: z.string().optional(),
allowImpersonation: z.boolean().optional().default(false), allowImpersonation: z.boolean().optional().default(false),
}); });
@@ -84,6 +85,7 @@ export const ProfileForm = () => {
image: data?.user?.image || "", image: data?.user?.image || "",
currentPassword: "", currentPassword: "",
allowImpersonation: data?.user?.allowImpersonation || false, allowImpersonation: data?.user?.allowImpersonation || false,
name: data?.user?.name || "",
}, },
resolver: zodResolver(profileSchema), resolver: zodResolver(profileSchema),
}); });
@@ -97,6 +99,7 @@ export const ProfileForm = () => {
image: data?.user?.image || "", image: data?.user?.image || "",
currentPassword: form.getValues("currentPassword") || "", currentPassword: form.getValues("currentPassword") || "",
allowImpersonation: data?.user?.allowImpersonation, allowImpersonation: data?.user?.allowImpersonation,
name: data?.user?.name || "",
}, },
{ {
keepValues: true, keepValues: true,
@@ -119,6 +122,7 @@ export const ProfileForm = () => {
image: values.image, image: values.image,
currentPassword: values.currentPassword || undefined, currentPassword: values.currentPassword || undefined,
allowImpersonation: values.allowImpersonation, allowImpersonation: values.allowImpersonation,
name: values.name || undefined,
}) })
.then(async () => { .then(async () => {
await refetch(); await refetch();
@@ -128,6 +132,7 @@ export const ProfileForm = () => {
password: "", password: "",
image: values.image, image: values.image,
currentPassword: "", currentPassword: "",
name: values.name || "",
}); });
}) })
.catch(() => { .catch(() => {
@@ -167,6 +172,19 @@ export const ProfileForm = () => {
className="grid gap-4" className="grid gap-4"
> >
<div className="space-y-4"> <div className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="email" name="email"

View File

@@ -26,7 +26,7 @@ export const ShowServerActions = ({ serverId }: Props) => {
View Actions View Actions
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-xl overflow-y-auto max-h-screen"> <DialogContent className="sm:max-w-xl">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<DialogTitle className="text-xl">Web server settings</DialogTitle> <DialogTitle className="text-xl">Web server settings</DialogTitle>
<DialogDescription>Reload or clean the web server.</DialogDescription> <DialogDescription>Reload or clean the web server.</DialogDescription>

View File

@@ -20,7 +20,9 @@ export const ToggleDockerCleanup = ({ serverId }: Props) => {
}, },
); );
const enabled = data?.user.enableDockerCleanup || server?.enableDockerCleanup; const enabled = serverId
? server?.enableDockerCleanup
: data?.user.enableDockerCleanup;
const { mutateAsync } = api.settings.updateDockerCleanup.useMutation(); const { mutateAsync } = api.settings.updateDockerCleanup.useMutation();

View File

@@ -99,7 +99,7 @@ export const EditScript = ({ serverId }: Props) => {
<FileTerminal className="size-4 text-muted-foreground" /> <FileTerminal className="size-4 text-muted-foreground" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl overflow-x-hidden"> <DialogContent className="sm:max-w-5xl overflow-x-hidden">
<DialogHeader> <DialogHeader>
<DialogTitle>Modify Script</DialogTitle> <DialogTitle>Modify Script</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -22,7 +22,7 @@ export const GPUSupportModal = () => {
<span>GPU Setup</span> <span>GPU Setup</span>
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-4xl overflow-y-auto max-h-screen"> <DialogContent className="sm:max-w-4xl">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
Dokploy Server GPU Setup Dokploy Server GPU Setup

View File

@@ -1,3 +1,11 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { PlusIcon } from "lucide-react";
import Link from "next/link";
import { useTranslation } from "next-i18next";
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 { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -30,14 +38,6 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PlusIcon } from "lucide-react";
import { useTranslation } from "next-i18next";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const Schema = z.object({ const Schema = z.object({
name: z.string().min(1, { name: z.string().min(1, {
@@ -218,7 +218,7 @@ export const HandleServers = ({ serverId }: Props) => {
</AlertBlock> </AlertBlock>
</div> </div>
{!canCreateMoreServers && ( {!canCreateMoreServers && (
<AlertBlock type="warning"> <AlertBlock type="warning" className="mt-4">
You cannot create more servers,{" "} You cannot create more servers,{" "}
<Link href="/dashboard/settings/billing" className="text-primary"> <Link href="/dashboard/settings/billing" className="text-primary">
Please upgrade your plan Please upgrade your plan

View File

@@ -88,7 +88,7 @@ export const SetupServer = ({ serverId }: Props) => {
Setup Server Setup Server
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-4xl overflow-y-auto max-h-screen "> <DialogContent className="sm:max-w-4xl ">
<DialogHeader> <DialogHeader>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
@@ -152,7 +152,7 @@ export const SetupServer = ({ serverId }: Props) => {
Copy Public Key ({server?.sshKey?.name}) Copy Public Key ({server?.sshKey?.name})
<button <button
type="button" type="button"
className=" right-2 top-8" className="right-2 top-8"
onClick={() => { onClick={() => {
copy( copy(
server?.sshKey?.publicKey || "Generate a SSH Key", server?.sshKey?.publicKey || "Generate a SSH Key",

View File

@@ -20,7 +20,7 @@ export const ShowDockerContainersModal = ({ serverId }: Props) => {
Show Docker Containers Show Docker Containers
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-7xl overflow-y-auto max-h-screen "> <DialogContent className="sm:max-w-7xl ">
<div className="grid w-full gap-1"> <div className="grid w-full gap-1">
<ShowContainers serverId={serverId} /> <ShowContainers serverId={serverId} />
</div> </div>

View File

@@ -21,7 +21,7 @@ export const ShowMonitoringModal = ({ url, token }: Props) => {
Show Monitoring Show Monitoring
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-7xl overflow-y-auto max-h-screen "> <DialogContent className="sm:max-w-7xl ">
<div className="flex gap-4 py-4 w-full"> <div className="flex gap-4 py-4 w-full">
<ShowPaidMonitoring BASE_URL={url} token={token} /> <ShowPaidMonitoring BASE_URL={url} token={token} />
</div> </div>

View File

@@ -20,7 +20,7 @@ export const ShowSchedulesModal = ({ serverId }: Props) => {
Show Schedules Show Schedules
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-5xl overflow-y-auto max-h-screen "> <DialogContent className="sm:max-w-5xl ">
<ShowSchedules id={serverId} scheduleType="server" /> <ShowSchedules id={serverId} scheduleType="server" />
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@@ -1,3 +1,9 @@
import { format } from "date-fns";
import { KeyIcon, Loader2, MoreHorizontal, ServerIcon } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { toast } from "sonner";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { DialogAction } from "@/components/shared/dialog-action"; import { DialogAction } from "@/components/shared/dialog-action";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@@ -27,12 +33,6 @@ import {
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { format } from "date-fns";
import { KeyIcon, Loader2, MoreHorizontal, ServerIcon } from "lucide-react";
import { useTranslation } from "next-i18next";
import Link from "next/link";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal"; import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal";
import { TerminalModal } from "../web-server/terminal-modal"; import { TerminalModal } from "../web-server/terminal-modal";
import { ShowServerActions } from "./actions/show-server-actions"; import { ShowServerActions } from "./actions/show-server-actions";
@@ -115,24 +115,6 @@ export const ShowServers = () => {
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-4 min-h-[25vh]"> <div className="flex flex-col gap-4 min-h-[25vh]">
{!canCreateMoreServers && (
<AlertBlock type="warning">
<div className="flex flex-row items-center gap-3 justify-center">
<span>
<div>
You cannot create more servers,{" "}
<Link
href="/dashboard/settings/billing"
className="text-primary"
>
Please upgrade your plan
</Link>
</div>
</span>
</div>
</AlertBlock>
)}
<Table> <Table>
<TableCaption> <TableCaption>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">

View File

@@ -20,7 +20,7 @@ export const ShowSwarmOverviewModal = ({ serverId }: Props) => {
Show Swarm Overview Show Swarm Overview
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-7xl overflow-y-auto max-h-screen "> <DialogContent className="sm:max-w-7xl ">
<div className="grid w-full gap-1"> <div className="grid w-full gap-1">
<SwarmMonitorCard serverId={serverId} /> <SwarmMonitorCard serverId={serverId} />
</div> </div>

View File

@@ -20,7 +20,7 @@ export const ShowTraefikFileSystemModal = ({ serverId }: Props) => {
Show Traefik File System Show Traefik File System
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-7xl overflow-y-auto max-h-screen "> <DialogContent className="sm:max-w-7xl ">
<ShowTraefikSystem serverId={serverId} /> <ShowTraefikSystem serverId={serverId} />
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@@ -1,3 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod";
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 { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
@@ -22,12 +28,6 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const Schema = z.object({ const Schema = z.object({
name: z.string().min(1, { name: z.string().min(1, {
@@ -108,7 +108,7 @@ export const CreateServer = ({ stepper }: Props) => {
<Card className="bg-background flex flex-col gap-4"> <Card className="bg-background flex flex-col gap-4">
<div className="flex flex-col gap-2 pt-5 px-4"> <div className="flex flex-col gap-2 pt-5 px-4">
{!canCreateMoreServers && ( {!canCreateMoreServers && (
<AlertBlock type="warning"> <AlertBlock type="warning" className="mt-2">
You cannot create more servers,{" "} You cannot create more servers,{" "}
<Link href="/dashboard/settings/billing" className="text-primary"> <Link href="/dashboard/settings/billing" className="text-primary">
Please upgrade your plan Please upgrade your plan
@@ -265,7 +265,7 @@ export const CreateServer = ({ stepper }: Props) => {
/> />
</form> </form>
<DialogFooter className="pt-5"> <DialogFooter>
<Button <Button
isLoading={form.formState.isSubmitting} isLoading={form.formState.isSubmitting}
disabled={!canCreateMoreServers} disabled={!canCreateMoreServers}

View File

@@ -1,18 +1,22 @@
import copy from "copy-to-clipboard";
import { CopyIcon, ExternalLinkIcon, Loader2 } from "lucide-react";
import Link from "next/link";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { CodeEditor } from "@/components/shared/code-editor"; import { CodeEditor } from "@/components/shared/code-editor";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import copy from "copy-to-clipboard";
import { ExternalLinkIcon, Loader2 } from "lucide-react";
import { CopyIcon } from "lucide-react";
import Link from "next/link";
import { useEffect, useRef } from "react";
import { toast } from "sonner";
export const CreateSSHKey = () => { export const CreateSSHKey = () => {
const { data, refetch } = api.sshKey.all.useQuery(); const { data, refetch } = api.sshKey.all.useQuery();
const generateMutation = api.sshKey.generate.useMutation(); const generateMutation = api.sshKey.generate.useMutation();
const { mutateAsync, isLoading } = api.sshKey.create.useMutation(); const { mutateAsync, isLoading } = api.sshKey.create.useMutation();
const hasCreatedKey = useRef(false); const hasCreatedKey = useRef(false);
const [selectedOption, setSelectedOption] = useState<"manual" | "provider">(
"manual",
);
const cloudSSHKey = data?.find( const cloudSSHKey = data?.find(
(sshKey) => sshKey.name === "dokploy-cloud-ssh-key", (sshKey) => sshKey.name === "dokploy-cloud-ssh-key",
@@ -60,89 +64,122 @@ export const CreateSSHKey = () => {
</div> </div>
) : ( ) : (
<> <>
<div className="flex flex-col gap-2 text-sm text-muted-foreground"> <div className="flex flex-col gap-4 text-sm text-muted-foreground">
<p className="text-primary text-base font-semibold"> <p className="text-primary text-base font-semibold">
You have two options to add SSH Keys to your server: Choose how to add SSH Keys to your server:
</p> </p>
<ul> {/* Radio button options */}
<li>1. Add The SSH Key to Server Manually</li> <div className="grid gap-2">
<RadioGroup
value={selectedOption}
onValueChange={(value) => {
setSelectedOption(value as "manual" | "provider");
}}
className="grid gap-3"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="manual" id="manual" />
<Label
htmlFor="manual"
className="text-primary font-medium cursor-pointer"
>
Add SSH Key to Server Manually
</Label>
</div>
<li> <div className="flex items-center space-x-2">
2. Add the public SSH Key when you create a server in your <RadioGroupItem value="provider" id="provider" />
preffered provider (Hostinger, Digital Ocean, Hetzner, etc){" "} <Label
</li> htmlFor="provider"
</ul> className="text-primary font-medium cursor-pointer"
>
<div className="flex flex-col gap-2 w-full border rounded-lg p-4"> Add SSH Key when creating server in your provider
<span className="text-base font-semibold text-primary"> </Label>
Option 1 </div>
</span> </RadioGroup>
<ul>
<li className="items-center flex gap-1">
1. Login to your server{" "}
</li>
<li>
2. When you are logged in run the following command
<div className="flex relative flex-col gap-4 w-full mt-2">
<CodeEditor
lineWrapping
language="properties"
value={`echo "${cloudSSHKey?.publicKey}" >> ~/.ssh/authorized_keys`}
readOnly
className="font-mono opacity-60"
/>
<button
type="button"
className="absolute right-2 top-2"
onClick={() => {
copy(
`echo "${cloudSSHKey?.publicKey}" >> ~/.ssh/authorized_keys`,
);
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="size-4" />
</button>
</div>
</li>
<li className="mt-1">
3. You're done, follow the next step to insert the details
of your server.
</li>
</ul>
</div> </div>
<div className="flex flex-col gap-2 w-full mt-2 border rounded-lg p-4">
<span className="text-base font-semibold text-primary"> {/* Content based on selected option */}
Option 2 {selectedOption === "manual" && (
</span> <div className="flex flex-col gap-2 w-full border rounded-lg p-4">
<div className="flex flex-col gap-4 w-full overflow-auto"> <span className="text-base font-semibold text-primary">
<div className="flex relative flex-col gap-2 overflow-y-auto"> Manual Setup Instructions
<div className="text-sm text-primary flex flex-row gap-2 items-center"> </span>
Copy Public Key <ul className="space-y-2">
<button <li className="items-center flex gap-1">
type="button" 1. Login to your server
className=" right-2 top-8" </li>
onClick={() => { <li>
copy( 2. When you are logged in run the following command
cloudSSHKey?.publicKey || "Generate a SSH Key", <div className="flex relative flex-col gap-4 w-full mt-2">
); <CodeEditor
toast.success("SSH Copied to clipboard"); lineWrapping
}} language="properties"
> value={`echo "${cloudSSHKey?.publicKey}" >> ~/.ssh/authorized_keys`}
<CopyIcon className="size-4 text-muted-foreground" /> readOnly
</button> className="font-mono opacity-60"
/>
<button
type="button"
className="absolute right-2 top-2"
onClick={() => {
copy(
`echo "${cloudSSHKey?.publicKey}" >> ~/.ssh/authorized_keys`,
);
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="size-4" />
</button>
</div>
</li>
<li className="mt-1">
3. You're done, follow the next step to insert the
details of your server.
</li>
</ul>
</div>
)}
{selectedOption === "provider" && (
<div className="flex flex-col gap-2 w-full border rounded-lg p-4">
<span className="text-base font-semibold text-primary">
Provider Setup Instructions
</span>
<div className="flex flex-col gap-4 w-full overflow-auto">
<div className="flex relative flex-col gap-2 overflow-y-auto">
<div className="text-sm text-primary flex flex-row gap-2 items-center">
Copy Public Key
<button
type="button"
className="right-2 top-8"
onClick={() => {
copy(
cloudSSHKey?.publicKey || "Generate a SSH Key",
);
toast.success("SSH Copied to clipboard");
}}
>
<CopyIcon className="size-4 text-muted-foreground" />
</button>
</div>
</div> </div>
</div> </div>
<p className="text-sm mt-2">
Use this public key when creating a server in your
preferred provider (Hostinger, Digital Ocean, Hetzner,
etc.)
</p>
<Link
href="https://docs.dokploy.com/docs/core/multi-server/instructions#requirements"
target="_blank"
className="text-primary flex flex-row gap-2 mt-2"
>
View Tutorial <ExternalLinkIcon className="size-4" />
</Link>
</div> </div>
<Link )}
href="https://docs.dokploy.com/docs/core/multi-server/instructions#requirements"
target="_blank"
className="text-primary flex flex-row gap-2"
>
View Tutorial <ExternalLinkIcon className="size-4" />
</Link>
</div>
</div> </div>
</> </>
)} )}

View File

@@ -60,7 +60,7 @@ export const WelcomeSuscription = () => {
return ( return (
<Dialog open={isOpen}> <Dialog open={isOpen}>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl min-h-[75vh]"> <DialogContent className="sm:max-w-7xl min-h-[75vh]">
{showConfetti ?? "Flaso"} {showConfetti ?? "Flaso"}
<div className="flex justify-center items-center w-full"> <div className="flex justify-center items-center w-full">
{showConfetti && ( {showConfetti && (
@@ -289,7 +289,7 @@ export const WelcomeSuscription = () => {
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h2 className="text-lg font-semibold">You're All Set!</h2> <h2 className="text-lg font-semibold">You're All Set!</h2>
<p className=" text-muted-foreground"> <p className="text-muted-foreground">
Did you know you can deploy any number of applications Did you know you can deploy any number of applications
that your server can handle? that your server can handle?
</p> </p>

View File

@@ -152,7 +152,7 @@ export const HandleSSHKeys = ({ sshKeyId }: Props) => {
</Button> </Button>
)} )}
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl"> <DialogContent className="sm:max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle>SSH Key</DialogTitle> <DialogTitle>SSH Key</DialogTitle>
<DialogDescription className="space-y-4"> <DialogDescription className="space-y-4">

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