Compare commits

...

819 Commits

Author SHA1 Message Date
Mauricio Siu
47db6831b4 Merge pull request #1461 from Dokploy/fix/envs-array-templates
feat(templates): support array-based environment variable configuration
2025-03-11 00:42:03 -06:00
Mauricio Siu
56cbd1abb3 test(templates): enhance secret key and base64 validation in template tests
Improve test coverage for secret key generation by:
- Adding more robust base64 validation checks
- Verifying base64 string format and length
- Ensuring generated keys meet specific cryptographic requirements
2025-03-11 00:41:53 -06:00
Mauricio Siu
cb40ac5c6b Merge branch 'canary' into fix/envs-array-templates 2025-03-11 00:38:50 -06:00
Mauricio Siu
7218b3f79b feat(templates): support array-based environment variable configuration
Add support for processing environment variables defined as an array in template configurations, allowing more flexible env var definitions with direct string values and variable interpolation
2025-03-11 00:38:10 -06:00
Mauricio Siu
19ea4d3fcd Merge pull request #1459 from Dokploy/fix/tweak-processor-template
refactor: update project name reference in compose template processing
2025-03-11 00:29:33 -06:00
Mauricio Siu
6edfd1e547 Merge branch 'canary' into fix/tweak-processor-template 2025-03-11 00:29:26 -06:00
Mauricio Siu
666a8ede97 chore(version): bump project version to v0.20.2
Update package.json version to reflect minor release
2025-03-11 00:29:07 -06:00
Mauricio Siu
08e4b8fe33 refactor: update project name reference in compose template processing
Change references from `compose.project.name` to `compose.appName` when processing compose templates to ensure correct project naming
2025-03-11 00:27:59 -06:00
Mauricio Siu
5fc265d14f Merge pull request #1458 from nktnet1/fix-domain-overflow
fix: truncate domain overflow for external links
2025-03-11 00:24:11 -06:00
Khiet Tam Nguyen
c3887af5d1 fix: truncate domain overflow for external links 2025-03-11 12:42:21 +11:00
Mauricio Siu
a6684af57e fix(templates): add null checks for template config properties
Prevent potential runtime errors by adding null checks for domains, env, and mounts in template processors
2025-03-10 03:25:04 -06:00
Mauricio Siu
8df2b20c3b Merge branch 'main' into canary 2025-03-10 02:41:10 -06:00
Mauricio Siu
f159dc11eb fix(traefik): increase service removal wait time to 15 seconds
Extend the timeout duration when removing Traefik service to ensure complete service removal and prevent potential initialization issues
2025-03-10 02:23:17 -06:00
Mauricio Siu
fce22ec1d0 fix(traefik): increase migration wait time for service removal
Adjust sleep/timeout duration in Traefik migration scripts to ensure proper service removal and container initialization
2025-03-10 01:54:25 -06:00
Mauricio Siu
e63eed57dd refactor: remove throw 2025-03-10 01:49:00 -06:00
Mauricio Siu
acc8ce80ad fix(backups): prevent error propagation in backup cleanup
Remove unnecessary error throwing in backup cleanup to allow partial success and logging
2025-03-10 01:48:28 -06:00
Mauricio Siu
e317772367 Merge pull request #1452 from Dokploy/canary
🚀 Release v0.20.0
2025-03-10 01:30:25 -06:00
Mauricio Siu
a15d9234be fix(preview): correctly access domain host in preview deployment
Update preview deployment to use `previewDeployment?.domain?.host` instead of `previewDeployment?.domain` when setting the DOKPLOY_DEPLOY_URL environment variable
2025-03-10 01:19:17 -06:00
Mauricio Siu
bd65f566fa Revert "Merge branch 'main' into canary"
This reverts commit 7c8594aadb, reversing
changes made to b8c1a9164a.
2025-03-10 01:17:25 -06:00
Mauricio Siu
7c8594aadb Merge branch 'main' into canary 2025-03-10 01:15:50 -06:00
Mauricio Siu
b8c1a9164a chore(version): bump project version to v0.20.0
- Update package.json version to reflect new release
- Prepare for next development iteration
2025-03-10 01:12:18 -06:00
Mauricio Siu
698118074a Merge pull request #1450 from Dokploy/feat/migration-templates
Feat/migration templates
2025-03-10 01:06:54 -06:00
Mauricio Siu
2fa691c5bd chore(templates): update template source URL to official domain
- Change base URL for template fetching from GitHub Pages to official templates domain
- Update both `fetchTemplatesList` and `fetchTemplateFiles` functions
- Ensure consistent template source URL across template-related functions
2025-03-10 01:06:31 -06:00
Mauricio Siu
87b007201a refactor(templates): replace ${randomDomain} with ${domain} in template processing 2025-03-10 00:02:28 -06:00
Mauricio Siu
b3b9b1956c test(templates): remove console log in template processing test
- Remove unnecessary console.log statement in config template test
- Maintain clean test code without debugging output
- Ensure test readability and performance
2025-03-09 21:35:27 -06:00
Mauricio Siu
d42a859679 feat(templates): add JWT generation and expand template variable processing
- Implement generateJwt function for creating JWT tokens
- Add support for 'jwt' and 'jwt:length' template variables
- Introduce new base64 and password generation shortcuts
- Enhance template variable processing with additional utility functions
2025-03-09 21:27:45 -06:00
Mauricio Siu
3a1fa95d17 chore(dependencies): remove unused webpack and related dependencies
- Remove copy-webpack-plugin from package.json
- Simplify next.config.mjs by removing webpack configuration
- Clean up pnpm-lock.yaml by removing unnecessary webpack-related packages
- Streamline project dependencies and configuration
2025-03-09 21:19:14 -06:00
Mauricio Siu
a45af37b5d feat(templates): add utility functions for template variable generation
- Implement new utility functions in template processing
- Add support for generating UUID, timestamp, and random port
- Extend template variable processing capabilities
2025-03-09 21:18:05 -06:00
Mauricio Siu
53312f6fa7 test(templates): add test for template processing without variables
- Implement test case for processing templates with empty variables
- Verify correct population of domains, environment variables, and mounts
- Ensure template processing works when no custom variables are provided
2025-03-09 21:14:10 -06:00
Mauricio Siu
cd8b6145f6 refactor(templates): update import paths in template test file
- Adjust import statements to reflect new template processing module locations
- Maintain consistent import structure for template-related utilities
- Ensure test file compatibility with recent template processing refactoring
2025-03-09 21:10:50 -06:00
Mauricio Siu
d4a98eb85e refactor(templates): remove legacy template files and update project structure
- Delete all template-related files in `apps/dokploy/templates`
- Remove template image files from `apps/dokploy/public/templates`
- Update server-side template processing with new implementation
- Clean up unused configuration and utility files
2025-03-09 21:09:05 -06:00
Mauricio Siu
152b2e1a5d refactor(templates): replace Github icon with custom GithubIcon component
- Update icon import to use custom GithubIcon from data-tools-icons
- Remove redundant Github icon import
- Maintain consistent icon styling and component usage
2025-03-09 18:55:27 -06:00
Mauricio Siu
19827fce84 feat(templates): add loading state and error handling for template fetching
- Implement loading spinner during template retrieval
- Add error alert for template fetching failures
- Enhance user experience with informative loading and error messages
- Import Loader2 icon for loading state visualization
2025-03-09 18:53:13 -06:00
Mauricio Siu
58f4d3561e feat(compose): enhance template import with improved error handling and user experience
- Refactor import process to use dedicated `import` mutation
- Add warning alert about configuration replacement
- Implement form reset on successful import
- Improve error handling and user feedback
- Remove unnecessary console logs and update UI text
2025-03-09 18:29:20 -06:00
Mauricio Siu
791a6c6f35 feat(compose): add Docker Compose template import functionality
- Implement new ShowImport component for importing Docker Compose configurations
- Add processTemplate mutation to handle base64-encoded template processing
- Integrate import feature into Compose service management page
- Support parsing and displaying template details including domains, environment variables, and mounts
2025-03-09 18:10:58 -06:00
Mauricio Siu
7580a5dcd6 fix(templates): update template file and logo paths to use 'blueprints' directory
- Modify template logo URL to use 'blueprints' instead of 'templates'
- Update GitHub template file fetching to use 'blueprints' directory
- Ensure consistent path structure for template resources
2025-03-09 17:06:43 -06:00
Mauricio Siu
6def84d456 feat(templates): add custom base URL support for template management
- Implement dynamic base URL configuration for template fetching
- Add localStorage persistence for base URL
- Update template rendering to use dynamic base URL
- Modify API routes to support optional base URL parameter
- Enhance template browsing flexibility
2025-03-09 14:08:08 -06:00
Mauricio Siu
6e7e7b3f9a feat(templates): refactor template processing and GitHub template fetching
- Implement new template processing utility in `processors.ts`
- Simplify GitHub template fetching with a more lightweight approach
- Add comprehensive test suite for template processing
- Improve type safety and modularity of template-related functions
2025-03-09 13:50:34 -06:00
Mauricio Siu
466fdf20b8 Merge branch 'canary' into feat/migration-templates 2025-03-09 13:14:41 -06:00
Mauricio Siu
991141460b Merge pull request #1448 from Dokploy/feat/autocomplete
feat(ui): add Docker Compose file editor autocomplete
2025-03-09 13:09:25 -06:00
Mauricio Siu
1a060d4204 fix(ui): improve Docker Compose autocomplete formatting
- Add space after colon in Docker Compose service options
- Remove unnecessary console.log statement
2025-03-09 13:02:30 -06:00
Mauricio Siu
64643c11aa feat(ui): add Docker Compose file editor autocomplete
- Implement Docker Compose file autocompletion in CodeMirror
- Add comprehensive suggestions for top-level and service-level keys
- Include a JSON schema for Docker Compose file validation
- Enhance code editor with intelligent YAML editing support
2025-03-09 13:00:22 -06:00
Mauricio Siu
b73bb0db5f Merge branch 'canary' into feat/migration-templates 2025-03-09 12:36:14 -06:00
Mauricio Siu
6287f3be4a Merge pull request #1371 from Dokploy/1345-domain-not-working-after-server-restart-or-traefik-reload
refactor(traefik): migrate from Docker Swarm service to standalone co…
2025-03-09 12:00:11 -06:00
Mauricio Siu
978cd61592 Merge pull request #1446 from Dokploy/feat/latest-n-backups
Feat/latest n backups
2025-03-09 11:57:57 -06:00
Mauricio Siu
6467ce0a24 feat(backups): improve backup retention across different database types
- Add serverId parameter to keepLatestNBackups function
- Execute backup retention commands on the specific server for each database type
- Remove global backup retention call in favor of per-database type retention
2025-03-09 11:54:36 -06:00
Mauricio Siu
f9f70efd2f Merge pull request #1447 from gentslava/canary
fix(ui): sorting
2025-03-09 11:38:32 -06:00
Mauricio Siu
688601107c Merge branch 'canary' into vicke4/canary 2025-03-09 02:48:19 -06:00
Vyacheslav Shcherbinin
6b4ec55e64 fix(ui): sorting created at 2025-03-09 15:33:29 +07:00
Mauricio Siu
b7f63fdad4 refactor(traefik): improve migration and removal of Traefik services
- Update Traefik service detection and removal logic in server and traefik setup
- Use Docker service and container inspection methods for more robust service management
- Add graceful migration and removal of existing Traefik services
- Simplify image pulling and service removal process
2025-03-09 02:32:02 -06:00
Mauricio Siu
404579b434 Merge pull request #1445 from gentslava/fix/autocomplete
fix(ui): Autocomplete
2025-03-09 01:43:26 -06:00
Vyacheslav Shcherbinin
b98d57e99a fix(ui): better autocomplete work 2025-03-09 14:22:06 +07:00
Vyacheslav Shcherbinin
dc5d79085c fix(ui): first letter case 2025-03-09 14:22:06 +07:00
Vyacheslav Shcherbinin
b95c90e6d8 fix(ui): disable default autocomplete 2025-03-09 14:22:00 +07:00
Mauricio Siu
988e5cb23e fix(traefik): improve error handling in container startup
Log Traefik container startup errors instead of throwing, preventing potential deployment interruptions
2025-03-09 01:14:45 -06:00
Mauricio Siu
19f574e168 Merge branch 'canary' into 1345-domain-not-working-after-server-restart-or-traefik-reload 2025-03-09 01:12:04 -06:00
Mauricio Siu
c462ad6144 Merge pull request #1431 from Gity37/fix-database-empty-backups
Database empty backups fix
2025-03-09 01:10:58 -06:00
Mauricio Siu
3acf80cec1 feat(ui): display Dokploy version in sidebar footer
- Uncomment and re-enable Dokploy version query
- Add version display in sidebar footer with responsive layout
- Show version text in both expanded and collapsed sidebar states
2025-03-09 00:02:35 -06:00
Mauricio Siu
0372372ae3 Merge pull request #1443 from Dokploy/873-can-monorepo-autoploy-base-on-different-paths
feat(applications): add watch paths for selective deployments
2025-03-08 23:49:48 -06:00
Mauricio Siu
492d51337c chore(github): remove debug console log in GitHub deployment handler 2025-03-08 23:46:06 -06:00
Mauricio Siu
467bca3efb feat(ui): add repository link buttons for git providers
- Implement "View Repository" links for GitHub, GitLab, Bitbucket, and Git providers
- Add repository icons and direct links to source repositories
- Support links for both application and compose service git provider forms
- Enhance user experience with quick access to repository pages
2025-03-08 23:45:21 -06:00
Mauricio Siu
9d50f384d1 chore(tests): add watchPaths to application test fixtures
- Update test fixtures for drop and traefik tests
- Add empty watchPaths array to base application configurations
- Ensure test files are consistent with recent watch paths feature implementation
2025-03-08 23:36:49 -06:00
Mauricio Siu
4371e7e033 chore(settings): add OpenAPI metadata for readStats endpoint 2025-03-08 23:34:57 -06:00
Mauricio Siu
c1aeb828d8 feat(applications): add watch paths for selective deployments
- Implement watch paths feature for GitHub and GitLab applications and compose services
- Add ability to specify paths that trigger deployments when changed
- Update database schemas to support watch paths
- Integrate micromatch for flexible path matching
- Enhance deployment triggers with granular file change detection
2025-03-08 23:32:08 -06:00
Mauricio Siu
1ad25ca6d1 Merge pull request #1442 from Dokploy/996-allow-customisation-of-a-domains-certresolver
feat(domains): add custom certificate resolver support
2025-03-08 21:22:59 -06:00
Mauricio Siu
1884a3d041 chore(preview): add previewCustomCertResolver to test files 2025-03-08 21:21:11 -06:00
Mauricio Siu
de48c81192 feat(preview): add custom certificate type for preview deployments 2025-03-08 21:16:18 -06:00
Mauricio Siu
e4197d6565 chore(domains): update domain configuration types and form handling
- Add `customCertResolver` to domain-related test files and form components
- Ensure consistent handling of optional custom certificate resolver
- Minor UI adjustment in code editor disabled state
2025-03-08 20:49:31 -06:00
Mauricio Siu
0c6625fff7 Merge pull request #1441 from eni4sure/update-isolated-deployment-label
fix(frontend): update isolated deployment label for clarity
2025-03-08 20:48:28 -06:00
Mauricio Siu
cc8ffca4d4 feat(domains): add custom certificate resolver support
- Extend domain configuration to support custom certificate resolvers
- Add new "custom" certificate type option in domain forms
- Update database schema and validation to include custom certificate resolver
- Implement custom certificate resolver handling in Traefik and Docker domain configurations
- Enhance domain management with more flexible SSL/TLS certificate options
2025-03-08 20:46:31 -06:00
Eniola Osabiya
c0b5f9e51a fix: update isolated deployment label for clarity 2025-03-08 20:40:14 -06:00
Mauricio Siu
4730845a40 fix(databases): improve rebuild database button loading state 2025-03-08 20:17:46 -06:00
Mauricio Siu
00fc1a9c96 Merge pull request #1440 from Dokploy/1120-rebuild-database
feat(databases): add database rebuild functionality
2025-03-08 20:15:57 -06:00
Mauricio Siu
624eedd74d feat(databases): add database rebuild functionality
- Implement RebuildDatabase component for all database types
- Create ShowDatabaseAdvancedSettings component to consolidate advanced settings
- Add rebuild API endpoints for Postgres, MySQL, MariaDB, MongoDB, and Redis
- Implement server-side database rebuild utility with volume and service removal
- Enhance database management with a dangerous zone for complete database reset
2025-03-08 20:12:28 -06:00
Mauricio Siu
c5272aa915 Merge pull request #1439 from Dokploy/981-ui-toggle-button-is-difficult-to-see-in-addedit-domain
fix(ui): update switch component background color for unchecked state
2025-03-08 19:32:15 -06:00
Mauricio Siu
2fdb7c6757 fix(ui): update switch component background color for unchecked state 2025-03-08 19:32:00 -06:00
Mauricio Siu
777aa3e4be feat(api): enhance API key display with code editor and clipboard copy 2025-03-08 19:26:18 -06:00
Mauricio Siu
55bab4bba4 Merge pull request #1438 from Dokploy/558-cancel-button-when-editing-environment-settings-and-other-text
feat(environment): add unsaved changes handling for environment settings
2025-03-08 19:18:36 -06:00
Mauricio Siu
6afd1bf531 feat(environment): add unsaved changes handling for environment settings
- Implement form change tracking for environment variables
- Add cancel and save buttons with conditional rendering
- Disable save button when no changes are detected
- Show unsaved changes warning in description
- Improve user experience with form state management
2025-03-08 19:17:59 -06:00
Mauricio Siu
62bd8e3c95 feat(services): add role-based delete service permissions
- Restrict bulk delete action to owners and users with delete service permissions
- Conditionally render delete button based on user role and authorization
- Improve service management security by implementing fine-grained access control
2025-03-08 18:51:59 -06:00
Mauricio Siu
85734c0a24 Merge pull request #1437 from Dokploy/700-reorganize-services-order
700 reorganize services order
2025-03-08 18:50:26 -06:00
Mauricio Siu
8d18aeda45 refactor(ui): improve responsive layout for project services view
- Update responsive breakpoints for service list layout
- Use more semantic breakpoint classes (xl, lg) for better responsiveness
- Adjust flex direction and alignment for improved mobile and desktop views
2025-03-08 18:50:09 -06:00
Mauricio Siu
45923d3a1f feat(services): add sorting functionality for services
- Implement local storage-based sorting for services
- Add sorting options by name, type, and creation date
- Provide ascending and descending sort order selection
- Enhance service list usability with dynamic sorting
2025-03-08 18:48:34 -06:00
Mauricio Siu
043843f714 Merge pull request #1436 from Dokploy/feat/add-bulk-delete
feat(services): add bulk delete functionality for services
2025-03-08 18:43:54 -06:00
Mauricio Siu
7dda252b7c feat(services): add bulk delete functionality for services
- Implement bulk delete feature for applications, compose, and various database services
- Add delete mutation endpoints for each service type
- Provide user-friendly bulk delete action with error handling and success notifications
- Integrate Trash2 icon for delete action in bulk service management
2025-03-08 18:43:37 -06:00
Mauricio Siu
bf0668c319 Merge pull request #1435 from Dokploy/969-move-services-between-projects
feat(services): add bulk service move functionality across projects
2025-03-08 18:40:33 -06:00
Mauricio Siu
fc1dbcf51a feat(services): improve bulk move project selection UX
- Add empty state handling when no other projects are available
- Disable move button when no target projects exist
- Provide clear guidance for users to create a new project before moving services
2025-03-08 18:40:23 -06:00
Mauricio Siu
b34987530e feat(services): add bulk service move functionality across projects
- Implement service move feature for applications, compose, databases, and other services
- Add move dialog with project selection for bulk service transfer
- Create move mutation endpoints for each service type
- Enhance project management with cross-project service relocation
- Improve user experience with error handling and success notifications
2025-03-08 18:39:02 -06:00
Mauricio Siu
ff8d922f2b Merge pull request #1434 from Dokploy/1301-add-information-tooltips-to-buttons
feat(ui): add tooltips to service action buttons for improved user gu…
2025-03-08 18:27:46 -06:00
Mauricio Siu
01c33ad98b feat(ui): add tooltips to service action buttons for improved user guidance
- Integrate tooltips for Deploy, Rebuild, Start, and Stop buttons across various service components
- Provide context-specific explanations for each action button
- Enhance user understanding of service management actions
- Consistent tooltip styling and implementation using TooltipProvider
2025-03-08 18:26:39 -06:00
Mauricio Siu
9816ecaea1 Merge pull request #1433 from Dokploy/1334-increase-the-size-of-environment-window
refactor(ui): improve environment code editor styling and layout
2025-03-08 18:09:04 -06:00
Mauricio Siu
832fa526dd refactor(ui): improve environment code editor styling and layout
- Adjust CodeEditor component wrapper and class names
- Enhance font and styling for environment configuration
- Optimize form item and control rendering
2025-03-08 18:08:49 -06:00
Mauricio Siu
2a5eceb555 Merge pull request #1432 from Dokploy/1315-show-containers-sorted-by-name
refactor(docker): sort container lists by name
2025-03-08 17:56:49 -06:00
Mauricio Siu
08d7c4e1c3 refactor(docker): sort container lists by name
- Add sorting to container retrieval methods in docker service
- Ensure consistent container list ordering across different container fetching functions
- Improve readability and predictability of container list results
2025-03-08 17:56:20 -06:00
Mauricio Siu
c89f957133 refactor(ui): enhance update server button and sidebar layout
- Improve UpdateServer component with flexible rendering and tooltip support
- Modify sidebar layout to integrate update server button more cleanly
- Add conditional rendering and styling for update availability
- Introduce more consistent button and tooltip interactions
2025-03-08 15:31:08 -06:00
Mauricio Siu
8ba3a42c1e Merge pull request #1430 from Dokploy/1278-dokploy-oom-issue-on-requests-tab
feat(monitoring): add date range filtering and log cleanup scheduling
2025-03-08 14:56:17 -06:00
César González Tarín
a96af6536b fix: database empty backups fix 2025-03-08 21:42:59 +01:00
Mauricio Siu
2c3ff5794d refactor(user): update log cleanup configuration
- Replace enableLogRotation boolean with logCleanupCron configuration
- Align with recent log scheduling and monitoring improvements
2025-03-08 14:23:52 -06:00
Mauricio Siu
673e0a6880 feat(monitoring): add date range filtering and log cleanup scheduling
- Implement date range filtering for access logs and request statistics
- Add log cleanup scheduling with configurable cron expression
- Update UI components to support date range selection
- Refactor log processing and parsing to handle date filtering
- Add new database migration for log cleanup cron configuration
- Remove deprecated log rotation management logic
2025-03-08 14:20:27 -06:00
Mauricio Siu
b64ddf1119 Merge pull request #1392 from ali-issa/feature/project-view-tab-reorg
feat: reorganize project view tabs into logical workflow groups #1261
2025-03-08 12:36:46 -06:00
Mauricio Siu
2f074ac734 Merge pull request #1405 from frostming/patch-1
feat: fallback to openai compatible provider if url host doesn't match
2025-03-07 00:56:33 -06:00
Mauricio Siu
96e3721b4b chore(ai): remove debug logging in model fetching 2025-03-07 00:56:12 -06:00
Mauricio Siu
b8e5cae88f feat(ai): improve model fetching and error handling
- Add server-side model fetching endpoint with flexible provider support
- Refactor client-side AI settings component to use new API query
- Implement dynamic header generation for different AI providers
- Enhance error handling and toast notifications
- Remove local model fetching logic in favor of server-side implementation
2025-03-07 00:55:11 -06:00
Mauricio Siu
fa20444a14 Merge pull request #1414 from gentslava/fix/template-superset
fix(template): superset
2025-03-07 00:12:43 -06:00
Mauricio Siu
668ccabec8 Merge pull request #1390 from hexaaagon/feat/zipline
feat(zipline): update zipline version
2025-03-07 00:12:19 -06:00
Mauricio Siu
aa07a0c574 Merge pull request #1417 from vinumzz/vinumzz-remove-shadow-monitoring
refactor: remove unnecessary extra shadow from monitoring page
2025-03-07 00:10:22 -06:00
Mauricio Siu
0b64b43376 Merge pull request #1415 from gentslava/feature/template-datalens
feat(template): DataLens
2025-03-07 00:10:03 -06:00
Mauricio Siu
5c65dc9a21 Update apps/dokploy/templates/datalens/docker-compose.yml 2025-03-07 00:06:20 -06:00
Mauricio Siu
58262606d4 Update apps/dokploy/templates/datalens/docker-compose.yml 2025-03-07 00:06:16 -06:00
Mauricio Siu
f73959db41 Update apps/dokploy/templates/datalens/docker-compose.yml 2025-03-07 00:06:10 -06:00
Mauricio Siu
e6c664e65f Update apps/dokploy/templates/datalens/docker-compose.yml 2025-03-07 00:06:06 -06:00
Mauricio Siu
36cc157566 Merge pull request #1355 from drudge/templates/hoarder
feat: add Hoarder template
2025-03-07 00:03:42 -06:00
Mauricio Siu
7e070623cc Merge pull request #1411 from gentslava/canary
fix: breadcrumbs UX
2025-03-06 23:58:42 -06:00
Mauricio Siu
b2c0a685f8 fix(destinations): validate server selection for cloud destinations 2025-03-06 23:57:39 -06:00
Mauricio Siu
c14528886d Merge pull request #1424 from Dokploy/1382-automated-postgres-backup-always-empty
feat(destinations): add createdAt timestamp and display creation date
2025-03-06 23:54:03 -06:00
Mauricio Siu
29eb490e2d feat(destinations): add createdAt timestamp and display creation date 2025-03-06 23:46:21 -06:00
Mauricio Siu
6166963b00 fix(gitlab): remove debug console logs from connection testing 2025-03-06 22:27:30 -06:00
Mauricio Siu
f544efed35 Merge pull request #1422 from Dokploy/1418-fetching-gitlab-repositories-0-found
fix(gitlab): update repository filtering and connection testing
2025-03-06 22:17:34 -06:00
Mauricio Siu
598d095241 fix(gitlab): update repository filtering and connection testing
- Change repository filtering to use 'user' kind instead of 'member'
- Add console logging for debugging GitLab provider and repository connection
- Ensure consistent filtering logic in both getGitlabRepositories and testGitlabConnection
2025-03-06 22:17:20 -06:00
Mauricio Siu
457a8e05fd chore(issue-template): update bug report label emoji 2025-03-06 22:05:31 -06:00
Mauricio Siu
3ca057c44a chore(issue-template): update bug report label and add cloud version option 2025-03-06 22:04:46 -06:00
Nicholas Penree
ad3a0198e9 feat: add Hoarder template 2025-03-06 08:23:49 -05:00
Peter Vinum
ab5f62604c refactor: remove unnecessary extra shadow from monitoring page 2025-03-06 14:08:23 +01:00
Vyacheslav Shcherbinin
bf9e886b9a Disable demo 2025-03-06 14:14:01 +07:00
Vyacheslav Shcherbinin
f5cd0fbdd8 Restart policy 2025-03-06 11:32:13 +07:00
Vyacheslav Scherbinin
8859cc97b4 fix: superset docker-compose 2025-03-06 10:46:10 +07:00
Vyacheslav Scherbinin
3bdd5e4dd0 Template DataLens 2025-03-06 10:34:13 +07:00
Vyacheslav Scherbinin
b0c710aa92 Tab instead space 2025-03-05 21:25:11 +07:00
Vyacheslav Scherbinin
c83d0a95b7 Remove ending separator 2025-03-05 20:53:38 +07:00
Vyacheslav Scherbinin
71ca5babfd Remove duplicate breadcrumb 2025-03-05 20:51:09 +07:00
Vyacheslav Scherbinin
f342613503 Text format 2025-03-05 20:27:16 +07:00
vicke4
cf4d6539e4 feat(server): function to keep only the latest N backups 2025-03-05 17:52:38 +05:30
vicke4
401f8d9be4 fix(ui): showing manual backup indicator only against the current backup 2025-03-05 17:52:38 +05:30
vicke4
1d2da0ac35 feat(ui): add keep latest backup count to show backups page 2025-03-05 17:52:38 +05:30
vicke4
d1391d7ddb feat(ui): add keep the latest input in create backups dialog 2025-03-05 17:52:38 +05:30
vicke4
b35bd9b719 feat(ui): coarsing number to avoid form validation error & placeholder change 2025-03-05 17:52:38 +05:30
vicke4
faab80bee1 feat(ui): add keep the latest input on backups dialog 2025-03-05 17:52:38 +05:30
vicke4
54a3c6efff feat(database): add keepLatestCount column to backup table 2025-03-05 17:52:38 +05:30
Frost Ming
efd176451f fix: default case correct 2025-03-05 16:19:28 +08:00
Frost Ming
a7fd64e019 fix: use a named case 2025-03-05 16:17:50 +08:00
Frost Ming
21c8b98f9c feat: fallback to openai compatible provider if url host doesn't match 2025-03-05 16:12:46 +08:00
Mauricio Siu
69dd704e1c Merge pull request #1403 from Dokploy/canary
🚀 Release v0.19.1
2025-03-05 00:55:21 -06:00
Mauricio Siu
6ff06576d0 fix(dockerfile): update Railpack installation script to use bash 2025-03-05 00:36:20 -06:00
Mauricio Siu
24cc08a1ac Merge pull request #1402 from Dokploy/feat/add-railpack
feat(application): add Railpack as a new build type
2025-03-05 00:24:37 -06:00
Mauricio Siu
e039826d50 chore(version): bump project version to v0.19.1 2025-03-05 00:21:36 -06:00
Mauricio Siu
a947b4915a Merge pull request #1398 from shaunjanssens/feature/bitbucket-branches-length
feat: Bitbucket branch length
2025-03-05 00:19:25 -06:00
Mauricio Siu
fc1d9ad202 Merge pull request #1399 from nktnet1/profile-moved-to-top
feat(ui): move profile to top of account dropdown
2025-03-05 00:18:43 -06:00
Mauricio Siu
5489e3b0a5 feat(application): add Railpack as a new build type
- Introduce Railpack as a new build method for applications
- Update database schema to include 'railpack' in buildType enum
- Add Railpack installation and validation scripts for servers
- Implement Railpack build and command generation utilities
- Update UI to include Railpack as a build option with a 'New' badge
2025-03-05 00:18:10 -06:00
Khiet Tam Nguyen
e43b907a8d feat(ui): move profile to first item in account dropdown 2025-03-04 22:42:04 +11:00
Shaun Janssens
62fae661a1 feat: bitbucket branch length 2025-03-04 10:50:42 +01:00
Ali Issa
6846e0e5a3 feat: reorganize project view tabs into logical workflow groups #1261
Restructure the project view tabs to follow a more intuitive user journey:
- Group tabs into "Initial Setup", "Deployment", and "Monitoring" sections
- Maintain "Advanced" as a standalone option
- Order tabs to match typical project workflow (configuration → deployment → monitoring)

This reorganization reduces cognitive load by grouping related functions,
minimizes tab switching during common tasks, and provides a clearer
mental model of the platform's workflow for new users.
2025-03-03 06:45:36 -05:00
Mauricio Siu
a27e523b0d Merge pull request #1389 from Dokploy/canary
🚀 Release v0.19.0
2025-03-02 23:33:13 -06:00
Hexaa
49d4cea06f feat(zipline): update zipline version 2025-03-03 12:17:42 +07:00
Mauricio Siu
5db7508530 feat(organization): prevent deletion of last owned organization 2025-03-02 22:32:56 -06:00
Mauricio Siu
4da4e1c17d feat(side-menu): restrict AI settings to owner role 2025-03-02 22:16:10 -06:00
Mauricio Siu
126dad498c chore(version): bump project version to v0.19.0 2025-03-02 21:59:31 -06:00
Mauricio Siu
8063673a7c refactor(traefik): streamline container removal and service management
- Remove dokploy-service before Traefik container initialization
- Simplify error handling and logging during container setup
- Add support for remote server service removal
2025-03-02 05:17:42 -06:00
Mauricio Siu
bf04dfa757 feat(traefik): add HTTP/3 support with UDP port configuration
- Introduce TRAEFIK_HTTP3_PORT environment variable
- Configure UDP port binding for HTTP/3
- Enable HTTP/3 with advertisedPort in Traefik websecure configuration
2025-03-02 04:35:58 -06:00
Mauricio Siu
d2e0536355 feat(traefik): add HTTP/3 support with UDP port configuration
- Introduce TRAEFIK_HTTP3_PORT environment variable
- Configure UDP port binding for HTTP/3
- Enable HTTP/3 with advertisedPort in Traefik websecure configuration
2025-03-02 04:31:06 -06:00
Mauricio Siu
f75d802749 Merge branch 'canary' into 1345-domain-not-working-after-server-restart-or-traefik-reload 2025-03-02 03:58:37 -06:00
Mauricio Siu
9c74b18e37 Merge pull request #1387 from Dokploy/1347-see-all-the-10-last-deployments-for-this-compose-show-all
feat(deployments): improve deployment list display and cleanup logic
2025-03-02 03:52:44 -06:00
Mauricio Siu
b13b906d75 feat(deployments): improve deployment list display and cleanup logic
- Add deployment index numbering in the UI
- Adjust deployment cleanup to retain 9 instead of 10 deployments
- Update deployment list rendering to show numbered status
2025-03-02 03:52:29 -06:00
Mauricio Siu
f63d582530 chore(dependencies): add Ollama AI provider to project dependencies
- Integrate Ollama AI provider in package.json
- Update pnpm-lock.yaml with Ollama AI provider dependency
2025-03-02 03:35:32 -06:00
Mauricio Siu
2ae14c65cf refactor(web-server): make type prop optional in ShowModalLogs component
- Update type prop to be optional in the Props interface
- Improve component flexibility by allowing undefined type
2025-03-02 03:24:29 -06:00
Mauricio Siu
0fa728d905 chore(dependencies): add AI SDK providers to project dependencies
- Integrate multiple AI SDK providers including Anthropic, Azure, Cohere, DeepInfra, Mistral, and OpenAI Compatible
- Update package.json and pnpm-lock.yaml with new AI SDK dependencies
2025-03-02 03:22:11 -06:00
Mauricio Siu
7f8f6ac64c refactor(traefik): migrate from Docker Swarm to standalone container
- Replace Docker service commands with standalone container management
- Update Traefik initialization to use container-based deployment
- Modify port inspection and environment variable retrieval methods
- Improve container creation and port binding logic
- Remove Swarm-specific constraints and deployment strategies
2025-03-02 03:14:54 -06:00
Mauricio Siu
3f45eb467b Merge branch 'canary' into 1345-domain-not-working-after-server-restart-or-traefik-reload 2025-03-02 02:34:10 -06:00
Mauricio Siu
b56272871f Merge pull request #1384 from Dokploy/mauricio/feature
Feat/AI
2025-03-02 02:31:24 -06:00
Mauricio Siu
1ffdebf60b Merge branch 'canary' into mauricio/feature 2025-03-02 02:16:45 -06:00
Mauricio Siu
0e81a027c1 Merge pull request #1385 from Dokploy/fix/migration
fix(database): ensure adminId is not null for certificates, notificat…
2025-03-02 02:16:06 -06:00
Mauricio Siu
cf3b3a2dcd fix(database): ensure adminId is not null for certificates, notifications, ssh-keys, and git providers 2025-03-02 02:15:49 -06:00
Mauricio Siu
a8fc27e830 feat(ai): add configuration files support for AI template generation
- Enhance template generation with configFiles feature
- Update StepTwo and StepThree components to display and edit configuration files
- Modify AI router and schemas to support configuration file mounting
- Refine AI service prompt to provide stricter guidelines for config file usage
2025-03-02 01:54:39 -06:00
Mauricio Siu
e7db3a196c feat(ai): enhance AI service prompt generation with strict guidelines
- Update project suggestion prompt to enforce plain text descriptions
- Add detailed rules for description, shortDescription, and response format
- Improve docker compose generation guidelines with comprehensive configuration rules
- Specify strict requirements for environment variables, volumes, and config files
2025-03-02 01:35:34 -06:00
Mauricio Siu
f78cda9cce feat(ai): update AI settings to use organization-based access control
- Refactor AI-related schemas and services to use organizationId instead of adminId
- Update AI router to check organization-level permissions
- Modify AI settings creation and retrieval to work with organization context
- Adjust server-side props and access checks for AI settings
2025-03-02 00:54:46 -06:00
Mauricio Siu
747c2137c9 Reapply "Merge branch 'canary' into kucherenko/canary"
This reverts commit e6cb6454db.
2025-03-02 00:30:02 -06:00
Mauricio Siu
e6cb6454db Revert "Merge branch 'canary' into kucherenko/canary"
This reverts commit 819822f30b, reversing
changes made to bda9b05134.
2025-03-02 00:26:59 -06:00
Mauricio Siu
819822f30b Merge branch 'canary' into kucherenko/canary 2025-03-02 00:23:58 -06:00
Mauricio Siu
5b3005eb89 Merge pull request #1376 from 190km/templates/listmonk-uploads
fix(templates): listmonk needed files volumes
2025-03-01 23:19:12 -06:00
Mauricio Siu
85a13eed00 Update apps/dokploy/templates/listmonk/docker-compose.yml 2025-03-01 23:19:02 -06:00
Mauricio Siu
e1b94dfe5b Merge pull request #1314 from vicke4/canary
feat: notifications to specific Telegram topics
2025-03-01 23:16:31 -06:00
Mauricio Siu
948fdbc22b chore(auth): remove default credentials from login and register forms 2025-03-01 23:09:43 -06:00
Mauricio Siu
eed38860b9 feat(database): add messageThreadId column to telegram table 2025-03-01 23:08:53 -06:00
Mauricio Siu
fefb5d14e0 Merge branch 'canary' into vicke4/canary 2025-03-01 23:05:30 -06:00
Mauricio Siu
8946f68af9 Merge pull request #1351 from Dokploy/feat/better-auth-2
Feat/Organizations & Better auth
2025-03-01 23:00:10 -06:00
Mauricio Siu
5fb2866660 feat(auth): conditionally disable authentication logger in production 2025-03-01 22:48:09 -06:00
Mauricio Siu
c51d63a4df chore: remove TODO comments and clean up code annotations 2025-03-01 22:21:03 -06:00
Mauricio Siu
13eccaf8d9 feat(user): add organization count check before user deletion 2025-03-01 22:14:12 -06:00
Mauricio Siu
adeb8498f9 refactor(auth): remove commented-out debug logging in TRPC context 2025-03-01 21:56:22 -06:00
Mauricio Siu
43599e7a97 fix(auth): handle null session and user with TypeScript ignore 2025-03-01 21:54:47 -06:00
Mauricio Siu
d7c94174b9 refactor(auth): simplify API key export in authentication module 2025-03-01 21:49:56 -06:00
Mauricio Siu
5c38a8265f feat(auth): improve email verification and invitation link generation for cloud environment 2025-03-01 21:47:31 -06:00
Mauricio Siu
a3362e0b15 feat(auth): add Google sign-in support for cloud environment 2025-03-01 21:30:30 -06:00
Mauricio Siu
0ad9233087 feat(logs): enable dynamic log rotation with database state management 2025-03-01 20:55:18 -06:00
Mauricio Siu
5dc5292928 feat(api): implement advanced API key management with granular controls 2025-03-01 19:58:15 -06:00
Mauricio Siu
5568629839 refactor: organize imports and improve template utility modules 2025-03-01 14:45:50 -06:00
Mauricio Siu
9aff4bc10b refactor: update template system with new configuration structure and processing 2025-03-01 03:11:29 -06:00
Mauricio Siu
49b37d531a feat: add GitHub-based template fetching and caching mechanism 2025-03-01 00:57:32 -06:00
190km
5aff345aa8 feat: added uploads volume for images upload 2025-02-26 22:36:03 +01:00
Mauricio Siu
37ca8f41f5 refactor(ui): remove loading text in sidebar layout 2025-02-25 23:37:22 -06:00
Mauricio Siu
cbec0603bd feat(ui): add loading state to sidebar layout 2025-02-25 23:36:53 -06:00
Mauricio Siu
8c2707c4ea refactor(organization): migrate to react-hook-form with zod validation 2025-02-25 23:33:26 -06:00
Mauricio Siu
7d77e14319 feat(organization): add logo support for organizations 2025-02-25 23:28:19 -06:00
Mauricio Siu
d3c59ff8af refactor(ui): enhance sidebar layout with responsive design and collapsed state 2025-02-25 23:13:55 -06:00
Mauricio Siu
7048e7e37e Merge branch 'canary' into feat/better-auth-2 2025-02-25 23:05:57 -06:00
Mauricio Siu
29c1e4691e feat(docker): add support for standalone container log retrieval 2025-02-25 23:04:19 -06:00
Mauricio Siu
203da1a8fe refactor(traefik): migrate from Docker Swarm service to standalone container 2025-02-25 22:51:02 -06:00
Mauricio Siu
0a6382a731 Merge pull request #1360 from Dokploy/remove-permit-root-login-advice
Remove permit root login advice
2025-02-23 18:11:58 -06:00
Mauricio Siu
d3b2cee7fb refactor(ui): improve log highlighting and template formatting 2025-02-23 18:11:27 -06:00
Mauricio Siu
125e44812b refactor(security): remove permit root login status row 2025-02-23 18:11:21 -06:00
Mauricio Siu
ac3378ccb8 feat(sponsors): add Synexa to sponsors section 2025-02-23 15:38:25 -06:00
Mauricio Siu
81e1161ba0 Merge pull request #1340 from wish-oss/glance-template
refactor: update glance template
2025-02-23 15:05:50 -06:00
Mauricio Siu
b35a8a1ecc Merge pull request #1343 from SkyfallWasTaken/canary
fix: make spacing between sidebar elements consistent
2025-02-23 14:53:09 -06:00
Mauricio Siu
9aa13c5ac3 Merge pull request #1356 from drudge/templates/pocket-id
feat: add Pocket ID template
2025-02-23 14:51:53 -06:00
Mauricio Siu
398fd54815 Merge pull request #1354 from drudge/templates/linkwarden
feat: add Linkwarden template
2025-02-23 14:46:41 -06:00
Mauricio Siu
7f4e4ab8d2 Merge pull request #1346 from Cohvir/canary
feat(template): add Wiki.js
2025-02-23 14:36:35 -06:00
Mauricio Siu
211697acaf Merge branch 'canary' into canary 2025-02-23 14:36:13 -06:00
Mauricio Siu
c0b64c6e55 Update apps/dokploy/templates/wikijs/docker-compose.yml 2025-02-23 14:35:39 -06:00
Mauricio Siu
5871a91da5 Update apps/dokploy/templates/wikijs/docker-compose.yml 2025-02-23 14:35:33 -06:00
Mauricio Siu
f4d13c3030 Update apps/dokploy/templates/wikijs/docker-compose.yml 2025-02-23 14:35:28 -06:00
Mauricio Siu
e00e19ec01 Merge pull request #1341 from drudge/templates/mailpit
feat: add Mailpit template
2025-02-23 14:30:38 -06:00
Mauricio Siu
c995268b39 Merge pull request #1349 from 190km/fix/logs-highlight-search-terms
fix: fixed highligh search terms color
2025-02-23 14:29:22 -06:00
Mauricio Siu
c8828b5620 Merge pull request #1357 from sondreal/fix-outline-typo
fixes typo outline->getoutline #1352
2025-02-23 14:10:12 -06:00
Mauricio Siu
ddd3101aeb Merge pull request #1348 from skyfall-sh/update-umami
chore: update umami to v2.16.1
2025-02-23 14:09:36 -06:00
sondreal
51310dae1d fixed the links 2025-02-23 19:15:17 +01:00
sondreal
0b7996adde removed my linting 2025-02-23 19:13:42 +01:00
sondreal
fb4b507250 fixes typo outline->getoutline #1352 2025-02-23 19:03:46 +01:00
Nicholas Penree
1294c2ad8e feat: add Pocket ID template 2025-02-23 12:07:54 -05:00
Nicholas Penree
733f9a0024 feat: add Linkwarden template 2025-02-23 10:51:50 -05:00
Mauricio Siu
73d3b58867 feat: add GitHub sign-in option for cloud environment 2025-02-23 01:59:00 -06:00
Mauricio Siu
0ea138571d refactor: update auth module to separate handler and API 2025-02-23 00:35:08 -06:00
Mauricio Siu
b1e7ffea21 chore: enable TypeScript size limit bypass in server tsconfig 2025-02-23 00:22:35 -06:00
Mauricio Siu
c0a7347ef5 chore: remove additional TypeScript configuration options in server tsconfig 2025-02-23 00:22:17 -06:00
Mauricio Siu
579faf2f58 chore: adjust TypeScript configuration in server tsconfig 2025-02-23 00:20:27 -06:00
Mauricio Siu
7429a1f65f chore: enable TypeScript declaration generation in server tsconfig 2025-02-23 00:16:51 -06:00
Mauricio Siu
716c1db799 Revert "chore: disable TypeScript declaration generation in server tsconfig"
This reverts commit 87836d23c3.
2025-02-23 00:13:35 -06:00
Mauricio Siu
9dd7f51eeb chore: disable TypeScript declaration generation in schedules tsconfig 2025-02-23 00:07:38 -06:00
Mauricio Siu
4a1a5a9bb1 chore: comment out database schema definitions in auth-schema 2025-02-23 00:04:24 -06:00
Mauricio Siu
87836d23c3 chore: disable TypeScript declaration generation in server tsconfig 2025-02-22 23:25:22 -06:00
Mauricio Siu
30cbad93d2 refactor: improve Traefik error handling in service initialization 2025-02-22 23:22:15 -06:00
Mauricio Siu
038b021043 Merge branch 'canary' into feat/better-auth-2 2025-02-22 23:22:11 -06:00
Mauricio Siu
0ec8e2baa1 chore: update GitHub workflow branch trigger for authentication feature branch 2025-02-22 23:20:27 -06:00
Mauricio Siu
3f2722f28d refactor: remove unused imports and simplify auth router 2025-02-22 22:38:38 -06:00
Mauricio Siu
47f7648cb3 feat: improve user profile update and password change functionality
This commit adds enhanced password change validation and handling:

- Add password change validation in user update route
- Implement password verification before allowing changes
- Update user schema to support optional password fields
- Fix token display in generate token component
- Disable migration script temporarily
2025-02-22 22:37:57 -06:00
Mauricio Siu
0478419f7c refactor: migrate authentication routes to user router and update related components
This commit continues the refactoring of authentication-related code by:

- Moving authentication routes from `auth.ts` to `user.ts`
- Updating import paths and function calls across components
- Removing commented-out authentication code
- Simplifying user-related queries and mutations
- Updating server-side authentication handling
2025-02-22 22:02:12 -06:00
Mauricio Siu
b00c12965a refactor: update reset password and authentication flows
This commit removes several authentication-related components and simplifies the password reset process:

- Removed login-2fa component
- Deleted confirm-email page
- Updated reset password logic to use Drizzle ORM directly
- Removed unused authentication-related functions
- Simplified server-side authentication routes
2025-02-22 21:09:21 -06:00
Mauricio Siu
8ab6d6b282 chore: clean up unused variables and improve error handling across codebase
This commit focuses on removing unused variables, adding placeholder error handling, and generally tidying up various files across the Dokploy application. Changes include:

- Removing unused imports and variables
- Adding placeholder error handling in catch blocks
- Cleaning up commented-out code
- Removing deprecated utility files
- Improving type safety and code consistency
2025-02-22 20:35:21 -06:00
190km
2470d672d4 fix: fixed highligh search terms color 2025-02-23 01:18:18 +00:00
Mahad Kalam
3403f8ab36 chore: update umami to v2.16.1 2025-02-23 00:47:04 +00:00
Mauricio Siu
1a415b96c9 refactor: remove unused auth service and clean up server-side code 2025-02-22 18:03:12 -06:00
Mauricio Siu
81a881b07e feat: enhance organization invitation UI and add organization details 2025-02-22 13:53:57 -06:00
Cohvir
baf555af52 feat(template): add Wiki.js 2025-02-22 14:16:14 +01:00
Mauricio Siu
c52725420e refactor: use organization context for server creation 2025-02-22 02:35:44 -06:00
Mauricio Siu
b02195db17 feat: add organization invitation system and update user profile management 2025-02-22 02:31:04 -06:00
Mauricio Siu
5ae103e779 refactor: update permission checks to use organization context 2025-02-21 00:48:04 -06:00
Mauricio Siu
a317f0c4cc refactor: simplify user permission checks across application 2025-02-21 00:40:35 -06:00
Mauricio Siu
24c9d3f7ad refactor: update user permissions and API queries 2025-02-21 00:30:55 -06:00
Mauricio Siu
63638bde33 refactor: consolidate database migration and clean up legacy user tables 2025-02-21 00:07:36 -06:00
Mauricio Siu
725bd1a381 refactor: migrate permissions from user_temp to member table 2025-02-21 00:00:22 -06:00
Mauricio Siu
790894ab93 refactor: migrate admin API calls to user router 2025-02-20 23:02:02 -06:00
Mahad Kalam
498a8523da fix: make spacing between sidebar elements consistent 2025-02-21 00:50:09 +00:00
Mauricio Siu
5a1145996d feat: add backup code authentication for 2FA login 2025-02-20 01:50:01 -06:00
Mauricio Siu
a9e12c2b18 refactor: update organization context in API routers 2025-02-20 01:42:35 -06:00
Nicholas Penree
c8b1fd36bd feat: add Mailpit template 2025-02-19 22:29:36 -05:00
vishalkadam47
609fea7daa refactor: update glance template 2025-02-20 08:35:14 +05:30
Mauricio Siu
b73e4102dd feat: add organizations and members 2025-02-17 02:48:42 -06:00
Mauricio Siu
c7d47a6003 refactor: update database foreign key constraints and user management 2025-02-17 00:30:15 -06:00
Mauricio Siu
8c28223343 refactor: remove 2fa migration 2025-02-17 00:10:34 -06:00
Mauricio Siu
7abe060fcf feat: enhance two-factor authentication and auth client implementation 2025-02-17 00:07:36 -06:00
Mauricio Siu
9e4efaeca6 Merge pull request #1331 from Dokploy/canary
🚀 Release v0.18.4
2025-02-16 21:46:57 -06:00
Mauricio Siu
56af89468a Merge pull request #1313 from nktnet1/superset-networks
docs(template): note on networking for superset
2025-02-16 21:34:40 -06:00
Mauricio Siu
b1502f5f82 Merge pull request #1329 from drudge/templates/registry
feat(template): add docker registry template
2025-02-16 21:33:54 -06:00
Mauricio Siu
c78b8cfead Update package.json 2025-02-16 21:30:57 -06:00
Mauricio Siu
0e8e92c715 refactor: add 2fa 2025-02-16 20:56:50 -06:00
Mauricio Siu
e1632cbdb3 refactor: update user and authentication schema with two-factor support 2025-02-16 15:32:57 -06:00
Nicholas Penree
48c4ec55f9 fix(template): switch outline to png for dark mode 2025-02-16 15:52:15 -05:00
Mauricio Siu
90156da570 refactor: remove tables 2025-02-16 14:11:47 -06:00
Mauricio Siu
9856502ece refactor: remove old references 2025-02-16 13:55:27 -06:00
Mauricio Siu
a8d1471b16 refactor: update 2025-02-16 13:28:29 -06:00
Mauricio Siu
27736c7c97 refactor: update role and validation handling across multiple pages 2025-02-16 03:06:22 -06:00
Mauricio Siu
e7db0ccb70 refactor: update invitation 2025-02-16 02:57:49 -06:00
Mauricio Siu
4a1a14aeb4 refactor: update 2025-02-15 23:24:45 -06:00
Mauricio Siu
ed62b4e1a3 refactor: lint 2025-02-15 23:01:44 -06:00
Mauricio Siu
515d65d993 refactor: adjust queries 2025-02-15 23:01:36 -06:00
Nicholas Penree
2ebb02dc20 feat(template): add docker registry template 2025-02-15 22:46:37 -05:00
Mauricio Siu
78c72b6337 refactor: update 2025-02-15 20:49:10 -06:00
Mauricio Siu
e3e35ce792 refactor: update to use organization resources 2025-02-15 20:43:23 -06:00
Mauricio Siu
6d0e195a4d refactor: update 2025-02-15 20:26:05 -06:00
Mauricio Siu
53ce5e57fa refactor: update organization 2025-02-15 20:25:58 -06:00
Mauricio Siu
87b12ff6e9 refactor: update 2025-02-15 20:06:33 -06:00
Mauricio Siu
8b71f963cc refactor: update logic 2025-02-15 19:35:22 -06:00
Mauricio Siu
1c5cc5a0db refactor: update roles 2025-02-15 19:23:08 -06:00
Mauricio Siu
d233f2c764 feat: adjust roles 2025-02-15 19:12:44 -06:00
Mauricio Siu
1bbb4c9b64 refactor: update migration 2025-02-15 18:13:20 -06:00
Mauricio Siu
9ca3ab3845 Merge pull request #1327 from Krobys/canary
feat(template): Add Appwrite template
2025-02-15 16:00:19 -06:00
Krobys
0baf9d8962 feat(template): add Appwrite 2025-02-15 21:46:18 +00:00
Mauricio Siu
71c609df0e Merge pull request #1326 from Dokploy/1304-preview-deployments-dont-work-when-enabling-auth-or-redirects
fix: inherit security & redirects from application
2025-02-15 15:24:36 -06:00
Mauricio Siu
450302b2c2 fix: inherit security & redirects from application 2025-02-15 15:23:33 -06:00
Mauricio Siu
71a007a4b3 Merge pull request #1325 from Dokploy/1257-main-server-error-error-http-code-409-unexpected---rpc-error
fix: handle race condition to catch recreation base containers
2025-02-15 14:26:52 -06:00
Mauricio Siu
871931b460 fix: handle race condition to catch recreation base containers 2025-02-15 14:23:54 -06:00
Mauricio Siu
3a7bb5016c Merge pull request #1324 from Dokploy/1320-missing-path-option-when-adding-a-template-in-dokploy
refactor: add missing path option
2025-02-15 14:09:33 -06:00
Mauricio Siu
23b59076b8 refactor: add missing path option 2025-02-15 13:35:25 -06:00
Mauricio Siu
2ddfc83f36 Merge pull request #1322 from drudge/ui/compose-monitoring-status
style(monitoring): use status badges for compose monitoring
2025-02-15 13:30:07 -06:00
Mauricio Siu
ba54124a56 Merge pull request #1321 from drudge/templates/outline
feat(template): add outline
2025-02-15 13:29:18 -06:00
Mauricio Siu
64bdf07dbd Merge pull request #1319 from mafrasil/feat-add-convex-tpl
feat(template): add convex.dev
2025-02-15 13:19:15 -06:00
Mauricio Siu
8366219266 refactor: format 2025-02-15 13:18:54 -06:00
Mauricio Siu
f38fb96eaf Merge branch 'canary' into feat-add-convex-tpl 2025-02-15 13:17:16 -06:00
Nicholas Penree
3b1ade804f style(monitoring): use status badges for compose monitoring 2025-02-15 14:16:30 -05:00
Mauricio Siu
fd69b45e5e refactor: update envs 2025-02-15 13:15:32 -06:00
Mauricio Siu
6ec60b6bab refactor: update validation 2025-02-15 13:14:48 -06:00
Mauricio Siu
12034e460e Merge pull request #1316 from mezotv/patch-2
feat(template): update plausible
2025-02-15 13:08:23 -06:00
Mauricio Siu
2b5a1d90b0 Merge pull request #1309 from jeffersoncbd/canary
feat: Add Trilium app template
2025-02-15 13:07:38 -06:00
Mauricio Siu
e7195c8acf Update apps/dokploy/templates/trilium/docker-compose.yml 2025-02-15 13:07:33 -06:00
Nicholas Penree
9ace0f38cd chore(lint): fix lint 2025-02-15 02:22:51 -05:00
Nicholas Penree
796e50ed5f feat(template): add outline 2025-02-15 02:22:37 -05:00
Mauricio Siu
55abac3f2f refactor: migrate endpoints 2025-02-14 02:52:37 -06:00
Mauricio Siu
b6c29ccf05 refactor: update 2025-02-14 02:40:11 -06:00
Mauricio Siu
ca217affe6 feat: update references 2025-02-14 02:18:53 -06:00
mafrasil
ed54df9bd2 feat(template): add convex.dev 2025-02-14 09:23:41 +04:00
vicke4
a4242d45d3 style: commitlint fix 2025-02-14 10:05:19 +05:30
vicke4
121755d9cc refactor: add field description to message thread id 2025-02-14 10:04:38 +05:30
Dominik Koch
bf6c2698d4 fix: update plausible version 2025-02-13 21:52:01 +01:00
Dominik Koch
bbdda014d8 feat(template): update plausible 2025-02-13 21:46:06 +01:00
vicke4
316dfa6b2e style: consistent formatting of code 2025-02-13 22:27:39 +05:30
vicke4
e228325e37 feat: notifications to specific Telegram topics 2025-02-13 22:20:52 +05:30
Khiet Tam Nguyen
d9c83b7010 docs(template): note on networking for superset 2025-02-13 20:36:03 +11:00
Mauricio Siu
5c24281f72 refactor: return correct information 2025-02-13 02:45:33 -06:00
Mauricio Siu
bc901bcb25 refactor: update 2025-02-13 02:36:08 -06:00
Mauricio Siu
7c0d223e17 refactor: add fields 2025-02-13 01:42:58 -06:00
Mauricio Siu
74ee024cf9 refactor: update temps 2025-02-13 01:24:25 -06:00
Mauricio Siu
140a871275 refactor: update 2025-02-13 01:21:49 -06:00
Mauricio Siu
d1f72a2e20 refactor: update migration 2025-02-13 00:57:22 -06:00
Mauricio Siu
0d525398a8 feat: migrate rest schemas 2025-02-13 00:45:29 -06:00
Mauricio Siu
7c62408070 refactor: delete 2025-02-13 00:38:39 -06:00
Mauricio Siu
23f1ce17de refactor: add migration 2025-02-13 00:38:22 -06:00
Mauricio Siu
60eee55f2d refactor: test migrastion 2025-02-12 23:41:04 -06:00
Mauricio Siu
209029214e Merge pull request #1307 from vytenisstaugaitis/canary
chore: update wordpress version to v6.7.1
2025-02-12 22:16:14 -06:00
Jefferson Carlos
9de3bf3c32 Add files via upload 2025-02-13 00:33:11 -03:00
Jefferson Carlos
2c65fc22b0 Update templates.ts 2025-02-13 00:32:45 -03:00
Jefferson Carlos
6688b14753 Create docker-compose.yml 2025-02-13 00:22:18 -03:00
Jefferson Carlos
6ea2a82bb0 Create index.ts for Trilium app 2025-02-13 00:21:23 -03:00
Mauricio Siu
8f562eefc1 Merge branch 'canary' into feat/better-auth 2025-02-12 20:56:23 -06:00
vytenisstaugaitis
e01347673e chore: update wordpress version to v6.7.1 2025-02-12 22:31:52 +02:00
Mauricio Siu
6179cef1ee refactor: update name 2025-02-10 02:13:52 -06:00
Mauricio Siu
b7112b89fd refactor: add migration 2025-02-10 00:39:46 -06:00
Mauricio Siu
0db9cb4418 Merge pull request #1300 from Dokploy/canary
🚀 Release v0.18.3
2025-02-10 00:35:31 -06:00
Mauricio Siu
030c8a312d Update package.json 2025-02-10 00:24:58 -06:00
Mauricio Siu
1db6ba94f4 refactor: remove 2025-02-09 21:36:36 -06:00
Mauricio Siu
afd3d2eea3 refactor: lint 2025-02-09 20:53:14 -06:00
Mauricio Siu
8bd72a8a34 refactor: add organizations system 2025-02-09 20:53:06 -06:00
Mauricio Siu
fafc238e70 refactor: migration 2025-02-09 18:56:17 -06:00
Mauricio Siu
c04bf3c7e0 feat: add migration 2025-02-09 18:19:21 -06:00
Mauricio Siu
6b9fd596e5 feat: add openalternative 2025-02-09 03:17:13 -06:00
Mauricio Siu
7e36433144 Merge pull request #1282 from wish-oss/feat/bulk-actions
feat: added bulk actions for services start and stop and added service status for domain dropdown
2025-02-09 03:07:01 -06:00
Mauricio Siu
0a6554c275 refactor: add loading action 2025-02-09 03:06:18 -06:00
Mauricio Siu
fcc55355f2 refactor: add catch to prevent throw error 2025-02-09 03:02:39 -06:00
Mauricio Siu
78e606876a Merge pull request #1297 from mohabgabber/canary
Update unsend version to v1.3.2
2025-02-09 02:37:31 -06:00
Mauricio Siu
7e99baa267 Merge branch 'canary' into canary 2025-02-09 02:37:23 -06:00
Mauricio Siu
92c03bb7cc Merge pull request #1276 from Dokploy/1004-network-conflict
1004 network conflict
2025-02-09 02:36:17 -06:00
Mauricio Siu
3a5ecb2f64 refactor: remove unused imports 2025-02-09 02:33:30 -06:00
Mauricio Siu
c0a00f4957 refactor: remove dokploy-network 2025-02-09 02:31:01 -06:00
Mauricio Siu
a8f94540f9 refactor: lint 2025-02-09 02:20:40 -06:00
Mauricio Siu
3e2cfe6eb8 refactor: agroupate utilities 2025-02-09 02:20:28 -06:00
Mohab Gabber
b2d5090b36 Merge branch 'canary' of https://github.com/mohabgabber/dokploy into canary 2025-02-09 03:22:27 +02:00
Mohab Gabber
0a0f53e9de chore: update unsend version to v1.3.2 2025-02-09 03:22:23 +02:00
Vishal kadam
17ce03e529 Merge branch 'Dokploy:canary' into feat/bulk-actions 2025-02-09 01:47:55 +05:30
Mauricio Siu
f44512a437 refactor: add condition to deploy on remote servers 2025-02-06 01:52:53 -06:00
Mauricio Siu
8379068fe3 refactor: remove services 2025-02-06 00:40:03 -06:00
Mauricio Siu
a71de72a3c refactor: remove services 2025-02-06 00:39:42 -06:00
Mauricio Siu
b024060eed refactor: delete unneeded container_name 2025-02-06 00:38:04 -06:00
Mauricio Siu
56b26ce0d5 refactor: use appname in network connect 2025-02-06 00:19:34 -06:00
Mauricio Siu
a9e3a65782 Merge branch 'canary' into 1004-network-conflict 2025-02-06 00:17:26 -06:00
Mauricio Siu
52e34b64a3 Merge pull request #1285 from laem/patch-1
Fix deploy env variable URL : should be a string, not an object
2025-02-06 00:16:43 -06:00
Mauricio Siu
bc8f54a2b9 Update packages/server/src/services/application.ts 2025-02-06 00:16:25 -06:00
Mauricio Siu
8b3e643ce7 Update packages/server/src/services/application.ts 2025-02-06 00:16:20 -06:00
Mauricio Siu
7a472df753 Merge pull request #1239 from NagariaHussain/template-frappe-hr
feat(template): frappe HR, open source HR & Payroll software
2025-02-06 00:14:59 -06:00
Mael
068dd33033 Fix deploy env variable URL : should be a string, not an object 2025-02-05 16:31:52 +01:00
vishalkadam47
bd809c8dca feat: added bulk actions for services start and stop and added service status for domain dropdown 2025-02-05 08:17:15 +05:30
Hussain Nagaria
48642979c5 chore: make erpnext template more configurable 2025-02-04 17:17:43 +05:30
Hussain Nagaria
46411a5f4e fix: create site should use configured db 2025-02-04 14:30:55 +05:30
Hussain Nagaria
82cf0643d7 fix: site volume configurable 2025-02-04 14:15:47 +05:30
Hussain Nagaria
65780ee852 feat: make db configurable 2025-02-04 13:57:49 +05:30
Mauricio Siu
0f99ca9c67 Merge pull request #1280 from Dokploy/canary
🚀 Release v0.18.2
2025-02-03 21:51:23 -06:00
Mauricio Siu
9d988c9a9b Update package.json 2025-02-03 21:49:20 -06:00
Mauricio Siu
eb211b933e Merge pull request #1277 from Blueshadow58/revert-1259-pocketbase
revert "feat<templates>: Updated PocketBase version to 0.25.0" #1259
2025-02-03 21:47:59 -06:00
Franco Gamonal
20eb6d7985 Revert "feat<templates>: Updated PocketBase version to 0.25.0" 2025-02-03 10:27:35 -03:00
Mauricio Siu
d424524d69 refactor: lint 2025-02-03 00:57:27 -06:00
Mauricio Siu
6f2148c060 feat: add deployable option to randomize and prevent colission in duplicate templates 2025-02-03 00:57:18 -06:00
Mauricio Siu
54b9f7b699 Merge pull request #1275 from Dokploy/canary
🚀 Release v0.18.1
2025-02-02 22:19:56 -06:00
Mauricio Siu
97b77e526d Merge pull request #1274 from Dokploy/fix/compose-builder
fix: add missing command
2025-02-02 22:19:05 -06:00
Mauricio Siu
077f47f2d8 chore: bump version 2025-02-02 22:18:47 -06:00
Mauricio Siu
5d6847b970 fix: add missing command 2025-02-02 22:18:21 -06:00
Mauricio Siu
cbc74b1c5e Merge pull request #1272 from Dokploy/canary
🚀 Release v0.18.0
2025-02-02 20:41:27 -06:00
Mauricio Siu
2630a73bd8 Merge pull request #1268 from mohabgabber/canary
chore: update unsend image version to v1.2.5
2025-02-02 20:26:24 -06:00
Mauricio Siu
df5ef4a34e Merge pull request #1269 from nktnet1/superset-unofficial-template
fix(template): superset SQLALCHEMY_EXAMPLES_URI env + warning
2025-02-02 20:26:11 -06:00
Mauricio Siu
48a8c6021d chore: bump version 2025-02-02 20:25:45 -06:00
Mauricio Siu
d84a22fa72 feat: add monitoring arm-amd version 2025-02-02 20:23:49 -06:00
Mauricio Siu
1661022d56 Fix/monitoring (#1271)
* refactor: make request to dokploy server to proxy requests

* refactor: lint

* refactor: use dokploy/monitoring tag image

* refactor: use canary in development or canary tags

* refactor: adjust logic

* refactor: update name
2025-02-02 19:51:19 -06:00
Mauricio Siu
2a295d6492 Merge branch 'canary' into fix/monitoring 2025-02-02 19:50:28 -06:00
Mauricio Siu
51851567db refactor: update name 2025-02-02 19:49:30 -06:00
Mauricio Siu
d1aaeb9a7b refactor: adjust logic 2025-02-02 19:43:28 -06:00
Mauricio Siu
f9b4035c20 refactor: use canary in development or canary tags 2025-02-02 19:41:07 -06:00
Mauricio Siu
d492ff87f2 Fix/monitoring (#1270)
* refactor: make request to dokploy server to proxy requests

* refactor: lint

* refactor: use dokploy/monitoring tag image
2025-02-02 19:35:36 -06:00
Mauricio Siu
f638f49ab6 refactor: use dokploy/monitoring tag image 2025-02-02 19:33:52 -06:00
Mauricio Siu
d1610855bb refactor: lint 2025-02-02 19:32:45 -06:00
Mauricio Siu
0c8c0844b1 refactor: make request to dokploy server to proxy requests 2025-02-02 19:32:37 -06:00
Tam Nguyen
98b19bb433 chore(template): resolve conflicts with main 2025-02-03 11:54:09 +11:00
Tam Nguyen
d8c5244d19 fix(template): superset SQLALCHEMY_EXAMPLES_URI env + warning 2025-02-03 11:47:57 +11:00
Mohab Gabber
7bb8456cb7 chore: update unsend image version to v1.2.5 2025-02-03 02:13:44 +02:00
Mauricio Siu
74a0f5e992 Feat/monitoring (#1267) Cloud Version
* feat: add start monitoring remote servers

* reafctor: update

* refactor: update

* refactor: update

* refactor: update

* refactor: update

* refactor: update

* refactor: update

* refactor:

* refactor: add metrics

* feat: add disk monitoring

* refactor: translate to english

* refacotor: add stats

* refactor: remove color

* feat: add log server metrics

* refactor: remove unused deps

* refactor: add origin

* refactor: add logs

* refactor: update

* feat: add series monitoring

* refactor: add system monitoring

* feat: add benchmark to optimize data

* refactor: update fn

* refactor: remove comments

* refactor: update

* refactor: exclude items

* feat: add refresh rate

* feat: add monitoring remote servers

* refactor: update

* refactor: remove unsued volumes

* refactor: update monitoring

* refactor: add more presets

* feat: add container metrics

* feat: add docker monitoring

* refactor: update conversion

* refactor: remove unused code

* refactor: update

* refactor: add docker compose logs

* refactor: add docker cli

* refactor: add install curl

* refactor: add get update

* refactor: add monitoring remote servers

* refactor: add containers config

* feat: add container specification

* refactor: update path

* refactor: add server filter

* refactor: simplify logic

* fix: verify if file exist before get stats

* refactor: update

* refactor: remove unused deps

* test: add test for containers

* refactor: update

* refactor add memory collector

* refactor: update

* refactor: update

* refactor: update

* refactor: remove

* refactor: add memory

* refactor: add server memory usage

* refactor: change memory

* refactor: update

* refactor: update

* refactor: add container metrics

* refactor: comment code

* refactor: mount proc bind

* refactor: change interval with node cron

* refactor: remove opening file

* refactor: use streams

* refactor: remove unused ws

* refactor: disable live when is all

* refactor: add sqlite

* refactor: update

* feat: add golang benchmark

* refactor: update go

* refactor: update dockerfile

* refactor: update db

* refactor: add env

* refactor: separate logic

* refactor: split logic

* refactor: update logs

* refactor: update dockerfile

* refactor: hide .env

* refactor: update

* chore: hide ,.ebnv

* refactor: add end angle

* refactor: update

* refactor: update

* refactor: update

* refactor: update

* refactor: update

* refactor: update monitoring

* refactor: add mount db

* refactor: add metrics and url callback

* refactor: add middleware

* refactor: add threshold property

* feat: add memory and cpu threshold notification

* feat: send notifications to the server

* feat: add metrics for dokploy server

* refactor: add dokploy server to monitoring

* refactor: update methods

* refactor: add admin to useeffect

* refactor: stop monitoring containers if elements are 0

* refactor: cancel request if appName is empty

* refactor: reuse methods

* chore; add feat monitoring

* refactor: set base url

* refactor: adjust monitoring

* refactor: delete migrations

* feat: add columns

* fix: add missing flag

* refactor: add free metrics

* refactor: add paid monitoring

* refactor: update methods

* feat: improve ui

* feat: add container stats

* refactor: add all container metrics

* refactor: add color primary

* refactor: change default rate limiting refresher

* refactor: update retention days

* refactor: use json instead of individual properties

* refactor: lint

* refactor: pass json env

* refactor: update

* refactor: delete

* refactor: update

* refactor: fix types

* refactor: add retention days

* chore: add license

* refactor: create db

* refactor: update path

* refactor: update setup

* refactor: update

* refactor: create files

* refactor: update

* refactor: delete

* refactor: update

* refactor: update token metrics

* fix: typechecks

* refactor: setup web server

* refactor: update error handling and add monitoring

* refactor: add local storage save

* refactor: add spacing

* refactor: update

* refactor: upgrade drizzle

* refactor: delete

* refactor: uppgrade drizzle kit

* refactor: update search with jsonB

* chore: upgrade drizzle

* chore: update packages

* refactor: add missing type

* refactor: add serverType

* refactor: update url

* refactor: update

* refactor: update

* refactor: hide monitoring on self hosted

* refactor: update server

* refactor: update

* refactor: update

* refactor: pin node version
2025-02-02 14:08:06 -06:00
Mauricio Siu
8c69d2a085 Merge pull request #1265 from Dokploy/1199-error-log-spam-in-traefik-logs-err-errorservice-dokploy-traefik-error-port-is-missing
refactor: delete label
2025-02-01 23:05:24 -06:00
Mauricio Siu
c8a4a826ca refactor: delete label 2025-02-01 23:04:31 -06:00
Mauricio Siu
e1b114a63b Merge pull request #1264 from Dokploy/1202-dates-are-not-shown-in-logs
fix: don't cut log
2025-02-01 23:00:45 -06:00
Mauricio Siu
0b4d19abd6 fix: don't cut log 2025-02-01 23:00:28 -06:00
Mauricio Siu
1c0f6a8e60 Merge pull request #1262 from Dokploy/1235-monitoring-404-not-found---mcpuvalue-is-null
refactor: update docker stats
2025-02-01 21:49:03 -06:00
Mauricio Siu
c41aa0ccf7 refactor: uypdate 2025-02-01 19:31:44 -06:00
Mauricio Siu
96bb72eb99 refactor: update docker stats 2025-02-01 19:27:10 -06:00
Mauricio Siu
ee2fed07b2 Merge pull request #1252 from mateodemuynck/fix/stack-domains
fix: expose docker stack services
2025-02-01 13:50:19 -06:00
Mauricio Siu
af083ffa5d refactor: remove duplicate case 2025-02-01 13:38:24 -06:00
Mauricio Siu
f14ed5170d Merge pull request #1249 from ali-issa/fix/domains-page-ux-improvements
fix: improve domains page UX and button placement consistency
2025-02-01 13:32:45 -06:00
Mauricio Siu
cd1c7e60bf refactor: update domains 2025-02-01 13:32:10 -06:00
Mauricio Siu
79c6b7389c Merge pull request #1259 from Blueshadow58/pocketbase
feat<templates>: Updated PocketBase version to 0.25.0
2025-02-01 11:20:40 -06:00
Mauricio Siu
e48f1431a9 Merge pull request #1238 from wish-oss/feat/copy-ip
feat: added clipboard functionality to copy IP address from badge in services pages
2025-02-01 11:20:11 -06:00
blueshadow58
c2ecdb2d76 feat<templates>: Updated PocketBase version to 0.25.0 2025-02-01 13:25:33 -03:00
Vishal kadam
5c889e81a9 Merge branch 'Dokploy:canary' into feat/copy-ip 2025-02-01 14:12:07 +00:00
Mauricio Siu
407e2e1137 Merge pull request #1250 from MuhammadM1998/patch-1
chore: remove duplicated sentence in projects dialog
2025-02-01 02:41:21 -06:00
vishalkadam47
2c6c89e4c1 refactor: added copy-to-clipboard library and replaced navigator.clipboard.writeText with copy function 2025-01-31 20:31:16 +05:30
Vishal kadam
41a8014585 Merge branch 'Dokploy:canary' into feat/copy-ip 2025-01-31 14:12:31 +00:00
Motysten
fffe1d6249 fix: labels not added correctly when using "stack" type docker dompose file 2025-01-31 14:15:52 +01:00
Muhammad Mahmoud
cf0f5c8b97 chore: remove duplicated sentence 2025-01-31 13:55:18 +02:00
Ali Issa
777980618f fix: improve domains page UX and button placement consistency 2025-01-31 02:59:23 -05:00
Mauricio Siu
dcd1df31c7 Merge pull request #637 from xenonwellz/feat/stack-env-support
Feat: added env support to Dokploy stack compose
2025-01-31 01:38:29 -06:00
Mauricio Siu
7369b54f32 refactor: update 2025-01-31 01:20:52 -06:00
Mauricio Siu
009859faa9 refactor: add .env docker stack 2025-01-31 01:20:10 -06:00
Mauricio Siu
f7a29accb1 refactor: lint 2025-01-30 23:40:01 -06:00
Mauricio Siu
50c518a63a Merge branch 'canary' into feat/stack-env-support 2025-01-30 23:39:54 -06:00
Mauricio Siu
79fca72d06 Merge branch 'canary' into template-frappe-hr 2025-01-30 23:32:56 -06:00
Mauricio Siu
18c6d08b9a Merge pull request #1237 from bernabedev/canary
feat(template): add Formbricks template with Docker Compose and environment setup
2025-01-30 23:32:32 -06:00
Mauricio Siu
208094cd3e Update apps/dokploy/templates/formbricks/index.ts 2025-01-30 23:31:43 -06:00
Mauricio Siu
1342f73a02 Update apps/dokploy/templates/formbricks/index.ts 2025-01-30 23:31:39 -06:00
Mauricio Siu
1787c524f0 Update apps/dokploy/templates/formbricks/docker-compose.yml 2025-01-30 23:31:33 -06:00
Mauricio Siu
5899dc9394 Merge pull request #1114 from joaotonaco/feat/template-evolution-api
feat(template): added evolution api
2025-01-30 23:24:25 -06:00
Mauricio Siu
6b48c0f354 fix: correct env values 2025-01-30 23:24:12 -06:00
Mauricio Siu
33f3d1d87e Merge branch 'canary' into feat/template-evolution-api 2025-01-30 23:16:09 -06:00
Mauricio Siu
4bfb172373 Merge pull request #1232 from muhammetakalan/patch-1
fix: teable template
2025-01-30 23:11:34 -06:00
Mauricio Siu
6cf96df6ec Merge pull request #1231 from jannismilz/canary
feat: upgrade listmonk version
2025-01-30 23:09:01 -06:00
Hussain Nagaria
62a3707c10 feat(template): frappe HR, open source HR & Payroll software 2025-01-29 18:49:27 +05:30
Freilyn Bernabe
00d2b3b572 fix: changing the description of formbricks to english 2025-01-29 07:26:14 -04:00
vishalkadam47
d8f1548076 feat: added clipboard functionality to copy IP address from badge in services pages 2025-01-29 13:09:58 +05:30
Freilyn Bernabe
de4d1c0911 feat(template): add Formbricks template with Docker Compose and environment setup 2025-01-29 00:39:01 -04:00
Muhammet Emin Akalan
b96169fa55 fix: teable template 2025-01-28 15:31:20 +03:00
Jannis Milz
e21e0e1865 feat: Upgrade listmonk version and remove automatic admin credentials creation 2025-01-28 12:24:54 +01:00
Mauricio Siu
dc9a194bbe Merge pull request #1229 from nktnet1/shlink-template
fix(template): shlink version stable in index.ts
2025-01-27 22:57:48 -06:00
Khiet Tam Nguyen
27738d253e fix(template): shlink version stable in index.ts 2025-01-28 01:47:14 +11:00
Mauricio Siu
d37bde00bc Merge pull request #1226 from nktnet1/shlink-template
feat(template): added shlink, a url shortener service
2025-01-26 23:44:13 -06:00
Khiet Tam Nguyen
55fae23ce3 feat(template): added shlink, a url shortener service 2025-01-27 14:36:07 +11:00
Mauricio Siu
ea910db9d1 Merge pull request #1225 from Dokploy/canary
🚀 Release v0.17.9
2025-01-26 20:51:04 -06:00
Mauricio Siu
d43cd52762 refactor: update 2025-01-26 19:58:35 -06:00
Mauricio Siu
094491ecbf refactor: add missing deps 2025-01-26 19:47:26 -06:00
Mauricio Siu
3d5bed0915 chore: add missing dependency 2025-01-26 19:30:21 -06:00
Mauricio Siu
23de14f0b4 chore: bump version 2025-01-26 19:15:11 -06:00
Mauricio Siu
215811ae82 Merge pull request #1224 from Dokploy/1093-expose-deploy-preview-url-in-env
feat: add DOKPLOY_DEPLOY_URL in enviroment variables for previews
2025-01-26 19:14:01 -06:00
Mauricio Siu
34b4956630 feat: add DOKPLOY_DEPLOY_URL in enviroment variables for previews 2025-01-26 19:13:43 -06:00
Mauricio Siu
7ed0282ce1 Merge pull request #1223 from Dokploy/fix/remove-cron-jobs-after-delete
Fix/remove cron jobs after delete
2025-01-26 19:01:32 -06:00
Mauricio Siu
24327139b8 refactor: make optional field name 2025-01-26 19:00:58 -06:00
Mauricio Siu
0fb67ced5d fix: remove cron jobs after delete service 2025-01-26 18:59:27 -06:00
Mauricio Siu
1e56364f93 refactor: update typo 2025-01-26 18:26:49 -06:00
Mauricio Siu
f980e459d9 Merge pull request #1220 from bernabedev/canary
feat(template): added answer template
2025-01-26 17:57:40 -06:00
Mauricio Siu
3209550edc Update apps/dokploy/templates/answer/docker-compose.yml 2025-01-26 17:57:31 -06:00
Mauricio Siu
9835ead2b9 Merge pull request #1222 from Dokploy/1164-when-creating-a-service-and-selecting-a-server-display-description-information
refactor: add server IP address
2025-01-26 17:50:07 -06:00
Mauricio Siu
73f93f8a13 refactor: add server IP address 2025-01-26 17:49:51 -06:00
Mauricio Siu
46ddaa68fa Merge pull request #1221 from Dokploy/1170-0176-copy-to-clipboard-stopped-working-on-the-project-delete-confirm-dialog
refactor: add back delete with confirmation
2025-01-26 17:45:03 -06:00
Mauricio Siu
64e68cfde1 refactor: update styles 2025-01-26 17:44:31 -06:00
Mauricio Siu
36c1a615c6 refactor: use copy package 2025-01-26 17:42:04 -06:00
Mauricio Siu
eeb97645b5 refactor: add back delete with confirmation 2025-01-26 17:40:28 -06:00
Freilyn Bernabe
9a052c4657 Merge branch 'Dokploy:canary' into canary 2025-01-26 19:25:44 -04:00
Freilyn Bernabe
165cdd27da feat(template): added answer template 2025-01-26 19:22:17 -04:00
Mauricio Siu
13551f6065 Merge pull request #1219 from Dokploy/706-ability-to-use-a-custom-port-for-git
fix: set extracted port on ssh connection
2025-01-26 17:21:09 -06:00
Mauricio Siu
f2ad1c5a57 fix: set extracted port on ssh connection 2025-01-26 17:20:33 -06:00
Mauricio Siu
4d5565895c Merge pull request #1218 from Dokploy/1059-inconsistence-layout-resource
refactor: adjust order
2025-01-26 16:28:17 -06:00
Mauricio Siu
8e51dedb78 refactor: adjust order 2025-01-26 16:28:00 -06:00
Mauricio Siu
780fa6b9cd Merge pull request #1217 from Dokploy/1177-when-deploying-applications-through-autodeploy-docker-cannot-update
refactor: make less aggressive cleanups
2025-01-26 16:25:16 -06:00
Mauricio Siu
95e642e63a refactor: make less aggressive cleanups 2025-01-26 16:20:02 -06:00
Mauricio Siu
5801c69034 Merge pull request #1216 from Dokploy/1190-cant-reach-my-app-by-assigned-domain
refactor: add dokploy-network on new installations to prevent gateway…
2025-01-26 15:54:47 -06:00
Mauricio Siu
2c98b4267f refactor: add dokploy-network on new installations to prevent gateway 504 errors randomly 2025-01-26 15:53:50 -06:00
Mauricio Siu
1874ffaa55 Merge pull request #1215 from Dokploy/1210-search-repository-function-is-not-working-correctly
fix: search by name and not on url
2025-01-26 15:46:11 -06:00
Mauricio Siu
11c4101dc3 fix: search by name and not on url 2025-01-26 15:45:53 -06:00
Mauricio Siu
4c3b5ef271 Merge pull request #1214 from NagariaHussain/template-erpnext
feat: add erpnext template
2025-01-26 15:29:58 -06:00
Mauricio Siu
4105353109 Merge branch 'canary' into template-erpnext 2025-01-26 15:20:30 -06:00
Mauricio Siu
8a971072e4 Merge pull request #1205 from nktnet1/superset-avoid-host-conflict
fix(template): prepend superset_ to postgres/redis
2025-01-26 15:19:46 -06:00
Mauricio Siu
dab12d6162 Merge pull request #1204 from gentslava/fix/issue_1198-muliple_preview_deployments
fix: Multiple Preview Deployments
2025-01-26 15:19:00 -06:00
Mauricio Siu
8d31574e5f refactor: add continue to is cloud 2025-01-26 15:17:26 -06:00
Mauricio Siu
0012ca7357 Merge pull request #1203 from wish-oss/fix/xterm
fix: terminal paste visibility
2025-01-26 15:02:26 -06:00
Mauricio Siu
1aed53e6fe refactor: add missing load 2025-01-26 15:01:49 -06:00
Mauricio Siu
d6b966cfea refactor: merge canary 2025-01-26 15:01:30 -06:00
Mauricio Siu
772341fb1e refactor: remove unused settings 2025-01-26 14:57:33 -06:00
Mauricio Siu
e7a6247297 refactor: lint 2025-01-26 14:56:01 -06:00
Mauricio Siu
b1beb7b71b fix: adjust color paste on terminal 2025-01-26 14:55:22 -06:00
Mauricio Siu
6254644fa6 Merge pull request #1196 from rahadi23/bugfix/1165-navigation-items-permissions
fix(ui): add condition to show update server button to admin only
2025-01-26 14:30:39 -06:00
Mauricio Siu
bcb86f3d6d Merge pull request #1195 from bernabedev/canary
feat(template): added maybe finance
2025-01-26 14:30:17 -06:00
Mauricio Siu
b96103247a Merge branch 'canary' into canary 2025-01-26 14:30:11 -06:00
Mauricio Siu
87697147da Update apps/dokploy/templates/maybe/docker-compose.yml 2025-01-26 14:29:51 -06:00
Mauricio Siu
074e3b6ec6 Merge pull request #1194 from wish-oss/feat/spacedrive-template
feat: add spacedrive file manager template
2025-01-26 14:24:52 -06:00
Mauricio Siu
7061e06736 Merge branch 'canary' into feat/spacedrive-template 2025-01-26 14:24:46 -06:00
Mauricio Siu
de35812d5a Merge pull request #1193 from wish-oss/feat/domain-navigation
feat: restore domain dropdown menu functionality
2025-01-26 14:22:34 -06:00
Mauricio Siu
c5ac5f84b1 Merge pull request #1186 from gentslava/canary
feat(i18n): add missing keys to Russian translation
2025-01-26 14:21:20 -06:00
Mauricio Siu
2e42fa7014 Merge pull request #1185 from RoodyCode/patch-1
style: fix incosistent spacing in Card-Footer
2025-01-26 14:21:05 -06:00
Mauricio Siu
0fe8a1a221 Merge pull request #1183 from yni9ht/feat-alist-template
feat(template): add alist template
2025-01-26 14:20:15 -06:00
Hussain Nagaria
829e77a6b8 feat: add erpnext template
Co-authored-by: Revant <revant.one@gmail.com>
2025-01-26 21:50:10 +05:30
Khiet Tam Nguyen
adfe598671 fix(template): prepend superset_ to postgres/redis 2025-01-25 19:14:58 +11:00
Vyacheslav Shcherbinin
ac49235916 Pull Request actions filter 2025-01-25 10:51:22 +07:00
vishalkadam47
d7210e9d7b fix: terminal paste visibility 2025-01-25 00:42:46 +05:30
Rahadi Jalu
52ea9ffa67 fix(ui): add condition to show update server button to admin only 2025-01-24 15:05:02 +07:00
Freilyn Bernabe
fd570ff861 feat(template): added maybe finance 2025-01-23 23:41:58 -04:00
vishalkadam47
130567dd78 feat: add Spacedrive file manager template 2025-01-24 08:17:39 +05:30
vishalkadam47
0a826fbf1c feat: add dropdown menu for service domains and add download functionality for SSH keys 2025-01-24 06:13:54 +05:30
Vyacheslav Shcherbinin
081d08c20a Update ru translation 2025-01-23 20:20:59 +07:00
Roody
41cf3d7b77 style: fix incosistent spacing 2025-01-23 11:32:08 +01:00
yni9ht
b05af62b7b feat: add alist template 2025-01-23 14:58:45 +08:00
Mauricio Siu
bfec980e45 Merge pull request #1181 from Dokploy/canary
🚀 Release v0.17.8
2025-01-23 00:54:53 -06:00
Mauricio Siu
c6569f70e4 Merge pull request #1182 from Dokploy/fix/disable-turn-off-cleanup
fix: turnoff cleanup cache
2025-01-23 00:54:30 -06:00
Mauricio Siu
d5c9338f51 fix: turnoff cleanup cache 2025-01-23 00:53:21 -06:00
Mauricio Siu
dba39b6364 Merge pull request #1180 from Dokploy/feat/add-error-logs-remote-server
Feat/add error logs remote server
2025-01-23 00:50:35 -06:00
Mauricio Siu
22de0fef49 chore: bump version 2025-01-23 00:44:48 -06:00
Mauricio Siu
03e1c17675 feat: add remote logs error when is not reachable 2025-01-23 00:44:31 -06:00
Mauricio Siu
c94f03804b Merge pull request #1179 from Dokploy/canary
🚀 Release v0.17.7
2025-01-22 23:57:05 -06:00
Mauricio Siu
6edd2a81e5 refactor: lint 2025-01-22 23:45:03 -06:00
Mauricio Siu
fe5b8782e9 chore: bump version 2025-01-22 23:44:43 -06:00
Mauricio Siu
71f28fae70 Merge pull request #1176 from wish-oss/feat/refactor-templates
feat:update templates glance and homarr
2025-01-22 23:43:53 -06:00
Mauricio Siu
c44618aa95 Merge pull request #1169 from SHABIN-K/add-malayalam-language
style(i18n) add malayalamlanguage
2025-01-22 23:39:37 -06:00
Mauricio Siu
c7d86dd430 Merge pull request #1178 from Dokploy/1175-preview-deployment-not-found-correctly
fix: set right branch in preview remote deployments
2025-01-22 23:33:29 -06:00
Mauricio Siu
e50bbd1a6a fix: set right branch in preview remote deployments 2025-01-22 23:33:05 -06:00
vishalkadam47
d5ff91563a fix: docker-compose.yaml to docker-compose.yml 2025-01-23 10:08:50 +05:30
vishalkadam47
210fe0759c feat:update templates glance and homarr 2025-01-23 03:43:04 +05:30
Shabin k
edff66900e add malayalam support 2025-01-22 12:26:05 +05:30
Shabin k
4cf2177928 add malayalam support 2025-01-22 12:23:38 +05:30
Shabin k
4a8306b015 Merge branch 'Dokploy:canary' into add-malayalam-language 2025-01-22 12:22:54 +05:30
Shabin k
f92d6693c3 add malayalam support 2025-01-22 12:22:27 +05:30
Mauricio Siu
0fde5a74cc Merge pull request #1168 from Dokploy/canary
🚀 Release v0.17.6
2025-01-22 00:39:41 -06:00
Mauricio Siu
81248ed03f fix: add continue to process all applications 2025-01-22 00:39:28 -06:00
Mauricio Siu
c7d5900e11 chore: bump version 2025-01-22 00:25:25 -06:00
Mauricio Siu
d0608f43a9 Merge pull request #1166 from rahadi23/bugfix/1165-navigation-items-permissions
fix: filter navigation items based on user's permissions and role
2025-01-22 00:24:55 -06:00
Mauricio Siu
adaf12a9a4 refactor: update 2025-01-22 00:16:00 -06:00
Mauricio Siu
baa2ca20f4 Merge branch 'canary' into bugfix/1165-navigation-items-permissions 2025-01-22 00:15:31 -06:00
Mauricio Siu
537caf02e5 Merge pull request #1167 from Dokploy/fix/creation-users
refactor: make protected instead of admin
2025-01-22 00:14:49 -06:00
Mauricio Siu
02ff507094 refactor: update lint 2025-01-22 00:14:30 -06:00
Mauricio Siu
53df7d969e refactor: make protected instead of admin 2025-01-22 00:13:22 -06:00
Rahadi Jalu
9e6e68558a fix: adjust dialog title based on add/update condition 2025-01-22 11:26:18 +07:00
Rahadi Jalu
a2e9ea2986 fix: add condition to show create project button 2025-01-22 11:26:18 +07:00
Rahadi Jalu
026e1bece6 fix: filter navigation items based on user's permissions and role 2025-01-22 11:26:18 +07:00
Mauricio Siu
51f6e08e16 Merge pull request #1158 from nktnet1/superset-unofficial-template
feat(template): added apache superset (unofficial)
2025-01-21 21:59:04 -06:00
Mauricio Siu
c0f8218ad9 Merge pull request #1161 from nktnet1/volume-file-mount-content-line-clamp
fix(ui): volume file mount content, line clamp and preserve whitespace
2025-01-21 21:51:07 -06:00
Mauricio Siu
35dd6bff7a Merge pull request #1160 from nktnet1/fix-invisible-mount-path
fix(ui): volume mountPath undefined for file mount - meant to display filePath instead?
2025-01-21 21:50:33 -06:00
Mauricio Siu
8dad8fd008 Merge pull request #1162 from mikield/add-ukrainian-language
style(i18n) add ukrainian language
2025-01-21 21:31:33 -06:00
Tam Nguyen
52dbc0d8f1 fix(template): superset healthchecks 2025-01-22 09:14:21 +11:00
Vladyslav G
692f883064 style(i18n) add ukrainian language 2025-01-21 16:21:29 +01:00
Khiet Tam Nguyen
6d90e268f7 fix(ui): volume file mount content, line clamp and preserve whitespace 2025-01-22 01:09:44 +11:00
Khiet Tam Nguyen
1d86f1a0fc fix(template): added analytics tag to superset 2025-01-22 00:05:55 +11:00
Khiet Tam Nguyen
c7338983b8 refactor(ui): clearer ui display condition for volume mount display 2025-01-21 23:45:54 +11:00
Khiet Tam Nguyen
ce06cd42b3 fix(ui): show filePath instead of mountPath for file mounts 2025-01-21 23:41:05 +11:00
Khiet Tam Nguyen
1a44a0ea5a refactor(template): use dokploy mount volume for superset_config.py 2025-01-21 23:14:28 +11:00
Khiet Tam Nguyen
444121f8d8 fix(template): more appropriate tags for superset 2025-01-21 20:40:46 +11:00
Khiet Tam Nguyen
05a75edbec feat(template): added apache superset (unofficial) 2025-01-21 19:03:35 +11:00
Mauricio Siu
c91f5dfc68 Merge pull request #1149 from Dokploy/canary
🚀 Release v0.17.5
2025-01-19 13:27:06 -06:00
Mauricio Siu
f5d81f434c chore: bump package 2025-01-19 13:25:44 -06:00
Mauricio Siu
10353d1f29 Merge pull request #1147 from eltociear/patch-2
chore(logs): update utils.ts
2025-01-19 13:24:54 -06:00
Mauricio Siu
6226c75959 Merge pull request #1148 from Dokploy/fix/clean-cache
Fix/clean cache
2025-01-19 13:24:37 -06:00
Mauricio Siu
42c9cd5901 chore: bump package 2025-01-19 13:24:22 -06:00
Mauricio Siu
49edcdb99e fix: clean cache at the start of deployments 2025-01-19 13:22:23 -06:00
Ikko Eltociear Ashimine
ad71e8b36a chore(logs): update utils.ts
Exemple -> Example
2025-01-20 03:15:02 +09:00
Mauricio Siu
e2275100a9 Merge pull request #1146 from Dokploy/canary
🚀 Release v0.17.4
2025-01-19 11:44:43 -06:00
Mauricio Siu
b166cd5bfa refactor: add settings page to user nav 2025-01-19 11:30:44 -06:00
Mauricio Siu
0045608acc chore: remove release title 2025-01-19 11:22:31 -06:00
Mauricio Siu
b140e81210 chore: update release 2025-01-19 11:20:22 -06:00
Mauricio Siu
9298d6c693 Merge pull request #1144 from Dokploy/canary
🚀 Release v0.17.3
2025-01-19 11:11:36 -06:00
Mauricio Siu
23df3fba85 refactor: regenerate migrations 2025-01-19 11:01:41 -06:00
Mauricio Siu
93c7f1ce76 chore: update 2025-01-19 10:56:43 -06:00
Mauricio Siu
1323394589 chore: remove draft 2025-01-19 10:51:05 -06:00
Mauricio Siu
4f11fc2547 chore: remove spaces 2025-01-19 10:49:08 -06:00
Mauricio Siu
6d052ad455 chore: add quotes 2025-01-19 10:47:04 -06:00
Mauricio Siu
60748da144 chore: update 2025-01-19 10:45:51 -06:00
Mauricio Siu
1956836cde refactor: update 2025-01-19 10:45:12 -06:00
Mauricio Siu
7dca4fe430 refactor: update 2025-01-19 10:43:32 -06:00
Mauricio Siu
84690c5f75 chore: update pr 2025-01-19 10:41:44 -06:00
Mauricio Siu
95b67ef2e9 chore: update 2025-01-19 10:36:47 -06:00
Mauricio Siu
3f8bc47ce5 refactor: update 2025-01-19 10:35:07 -06:00
Mauricio Siu
498678c4ae Revert "refactor: update"
This reverts commit 3d602c232d.
2025-01-19 10:21:30 -06:00
Mauricio Siu
3d602c232d refactor: update 2025-01-19 10:19:59 -06:00
Mauricio Siu
94ffa7d578 chore: update 2025-01-19 10:15:28 -06:00
Mauricio Siu
64fc3c7677 chore: remove pr 2025-01-19 10:14:58 -06:00
Mauricio Siu
f27830daf0 refactor: update version 2025-01-19 10:08:53 -06:00
Mauricio Siu
3ec2e2dd1a Revert "chore: bump version"
This reverts commit 1e006cb094.
2025-01-19 10:06:50 -06:00
Mauricio Siu
1e006cb094 chore: bump version 2025-01-19 10:00:54 -06:00
Mauricio Siu
8aa655af2c chore: add pr 2025-01-19 10:00:09 -06:00
Mauricio Siu
65659e27f1 Merge pull request #1137 from Dokploy/feat/github-runners
Feat/GitHub runners
2025-01-19 09:58:27 -06:00
Mauricio Siu
4b6f9108d4 refactor: update 2025-01-19 02:56:08 -06:00
Mauricio Siu
1e4a41a8e3 chore: replace circle with github actions 2025-01-19 02:55:05 -06:00
Mauricio Siu
539aa7a85b refactor: print version 2025-01-19 02:47:14 -06:00
Mauricio Siu
b6ae502b92 refactor: update 2025-01-19 02:38:38 -06:00
Mauricio Siu
e82db47ec4 refactor: add github docker 2025-01-19 02:33:53 -06:00
Mauricio Siu
f9b4f008c2 Merge pull request #1134 from Dokploy/1031-excessive-unused-docker-cache-generated-by-dokploy-deployments
feat: add cleanup cache on deployments
2025-01-19 02:08:48 -06:00
Mauricio Siu
adb204ec1f refactor: add sidebar persistence 2025-01-19 01:44:42 -06:00
Mauricio Siu
5310a559b0 refactor: improve sidebar 2025-01-19 01:29:29 -06:00
Mauricio Siu
43b7db00f9 refactor: add missing values to test 2025-01-19 01:21:03 -06:00
Mauricio Siu
52c83fd6fc refactor: update text 2025-01-19 01:07:41 -06:00
Mauricio Siu
25a8df567e feat: add cleanup cache on deployments 2025-01-19 00:57:42 -06:00
Mauricio Siu
bda9b05134 refactor: add ts ignore 2025-01-18 23:54:39 -06:00
Mauricio Siu
e7329a727f refactor: use stepper 2025-01-18 23:49:47 -06:00
Mauricio Siu
e68465f9e6 refactor: improve error 2025-01-18 23:07:36 -06:00
Mauricio Siu
5e7d344110 feat: add missing functions 2025-01-18 22:58:27 -06:00
Mauricio Siu
87546b4558 feat: add domains 2025-01-18 22:55:35 -06:00
Mauricio Siu
08ab18eebf refactor: add many AI providers & improve prompt 2025-01-18 21:35:03 -06:00
Mauricio Siu
ad642ab4e0 refactor: update migrations 2025-01-18 18:29:28 -06:00
Mauricio Siu
d5d8064b38 Merge branch 'canary' into kucherenko/canary 2025-01-18 18:29:12 -06:00
Mauricio Siu
089274492d Merge pull request #1125 from SlavenIvanov/feat/add-couch-db-template
feat: added couchdb template
2025-01-18 17:39:12 -06:00
Mauricio Siu
65c0ea829f Merge branch 'canary' into feat/add-couch-db-template 2025-01-18 17:39:02 -06:00
Mauricio Siu
d060eec465 Update apps/dokploy/templates/couchdb/docker-compose.yml 2025-01-18 17:38:33 -06:00
Mauricio Siu
2dca0d343e Merge pull request #1069 from nktnet1/accessed-typo
chore: typo "accesed" changed to "accessed" for TS code
2025-01-18 17:35:04 -06:00
Mauricio Siu
a8f63bb4d3 Merge pull request #1131 from agustints/fix/db-backups-destination-url
fix: Update link text and destination for db backup settings
2025-01-18 17:34:09 -06:00
Mauricio Siu
cecd371988 Merge pull request #1081 from depado/gotify-notifications
feat(notifications): implement gotify provider
2025-01-18 17:33:55 -06:00
Mauricio Siu
6c4d94cb4f Merge pull request #1118 from izayl/feature/ci-skip
feat: add ci skip check
2025-01-18 17:30:41 -06:00
Mauricio Siu
c4e5c818f3 Merge branch 'canary' into gotify-notifications 2025-01-18 17:23:35 -06:00
Mauricio Siu
31a35d91e8 Merge pull request #1088 from shiqocred/canary
Update telegram message notification
2025-01-18 17:22:50 -06:00
Mauricio Siu
74a2f79a36 Merge pull request #1123 from nktnet1/it-tools-template
feat(template): added it-tools
2025-01-18 11:02:42 -06:00
Mauricio Siu
a8f8a727bd Merge pull request #1128 from TheLetslook/patch-1
feat(style): custom scrollbar styling
2025-01-18 11:00:42 -06:00
Mauricio Siu
e8f2ab35c9 Merge pull request #1121 from thebadking/canary
style: fix tablet and mobile (Create from Template)
2025-01-18 10:52:24 -06:00
depado
9806a5d607 feat(notifications): fix gotify style 2025-01-18 14:23:53 +01:00
depado
e25d0c0c68 feat(notifications): implement notifications for gotify 2025-01-18 14:23:27 +01:00
depado
1f8a476264 chore(lint): run biome 2025-01-18 14:23:27 +01:00
depado
cc473b3e87 feat(notifications): implement gotify provider 2025-01-18 14:23:27 +01:00
Agustin Tornielli
10b3543d39 fix: Update link text and destination for db backup settings 2025-01-18 01:19:33 -03:00
thebadking
0893149db0 style: grid fix 2025-01-17 20:45:17 +00:00
Vasiliy
e257f86194 feat(style): custom scrollbar styling 2025-01-17 21:49:44 +03:00
Slaven Ivanov
a9a0b4cb03 feat: added couchdb template 2025-01-17 18:09:02 +02:00
Khiet Tam Nguyen
c5073c9f30 feat(template): added it-tools 2025-01-18 01:02:39 +11:00
João Gabriel
34ab01fcae Merge branch 'canary' into feat/template-evolution-api 2025-01-17 10:38:14 -03:00
João Gabriel
df43f8318a fix: evolution api database provider 2025-01-17 10:37:01 -03:00
izayl
e69c602d1c chore: enhance deployment skip message to include reason for skipping 2025-01-17 15:47:18 +08:00
izayl
0116d995d9 test: extractCommitMessage 2025-01-17 15:32:59 +08:00
Mauricio Siu
ad479c4be1 Merge pull request #1112 from nktnet1/conduwuit-template
feat(template): added conduwuit
2025-01-16 22:24:46 -06:00
Mauricio Siu
f70192a71c refactor: lint 2025-01-16 22:24:31 -06:00
Mauricio Siu
abff70f081 refactor: lint 2025-01-16 22:24:17 -06:00
Mauricio Siu
9f03faaec2 Merge branch 'canary' into conduwuit-template 2025-01-16 22:24:03 -06:00
Mauricio Siu
1d760bd25f Merge pull request #1104 from nktnet1/cloudflared-template
feat(template): added cloudflared
2025-01-16 22:20:59 -06:00
Mauricio Siu
26a67dd175 Merge pull request #1119 from wish-oss/extrernal-link
refactor: update icon handling and restructure sidebar external links
2025-01-16 22:15:57 -06:00
thebadking
013ee89a56 style: fix tablet and mobile (Create from Template) 2025-01-16 21:12:11 +00:00
vishalkadam47
c3d3c7b96a refactor: update icon handling and restructure sidebar external links 2025-01-16 19:31:55 +05:30
izayl
4e724d88aa feat: add ci skip check 2025-01-16 16:26:28 +08:00
Mauricio Siu
351e4b6103 Merge pull request #1115 from TheLetslook/patch-1
fix: db type typo
2025-01-15 23:30:29 -06:00
Vasiliy
5b633ec6c5 fix: db type typo
Postgres -> Mongo
2025-01-14 23:25:22 +03:00
João Gabriel
a46cbf4f2c feat: evolution api template 2025-01-14 08:36:29 -03:00
Andrey Kucherenko
d98fc82fbf fix: code review issues 2025-01-14 08:34:56 +01:00
Khiet Tam Nguyen
ca9552d618 chore: sort conduwuit tags 2025-01-14 17:30:45 +11:00
Khiet Tam Nguyen
23b40c1aa7 feat(template): added conduwuit 2025-01-14 17:21:05 +11:00
Mauricio Siu
5e0b7ba143 Merge pull request #1109 from 190km/fix/sidebar
fix: fixed missing cluster side item
2025-01-13 23:50:02 -06:00
Mauricio Siu
cb1203e302 refactor: exclude cluster from cloud 2025-01-13 23:49:22 -06:00
Mauricio Siu
373c5bc507 Merge pull request #1106 from DJKnaeckebrot/fix/styling-after-sidebar-fixes
fix(style): clean up sidebar related style issue
2025-01-13 23:41:00 -06:00
Mauricio Siu
ee5afa4793 Merge pull request #1110 from szwabodev/fix/wslCheck
fix: isWSL check was not awaited
2025-01-13 22:58:15 -06:00
Mauricio Siu
7b9426586d Merge pull request #1107 from joaotonaco/refactor/improve_sidebar_animation
refactor(sidebar): improve animation
2025-01-13 22:38:41 -06:00
190km
772b24a415 fix: fixed missing cluster side item 2025-01-13 14:12:19 +00:00
UndefinedPony
1922437115 fix: isWSL check was not awaited causing always true 2025-01-13 14:48:13 +01:00
João Gabriel
c5eb8b087d refactor: transition logo size 2025-01-13 09:20:02 -03:00
João Gabriel
94ee5391fb refactor: apply ease-out to sidebar toggle animation 2025-01-13 09:19:32 -03:00
djknaeckebrot
b3ff14f792 style: clean up class names for consistency in dashboard components 2025-01-13 12:09:06 +01:00
djknaeckebrot
b68273c8ca style: adjust grid layout for monitoring card and clean up class names 2025-01-13 12:02:59 +01:00
djknaeckebrot
8b203c48b4 feat: update sidebar with Traefik File System title and add support link 2025-01-13 11:55:58 +01:00
djknaeckebrot
fb33a5b6a5 style: make sure card do use the full width of the div 2025-01-13 11:53:05 +01:00
Khiet Tam Nguyen
b17ab6d8cb feat(template): added cloudflared 2025-01-13 20:02:18 +11:00
Mauricio Siu
30d20bd267 Merge pull request #1101 from Dokploy/canary
v0.17.2
2025-01-13 02:28:37 -06:00
Mauricio Siu
f9b1c2575e refactor: lint 2025-01-13 02:28:00 -06:00
Mauricio Siu
b0b22224c3 revert: add missing installation buttons 2025-01-13 02:27:49 -06:00
shiqocred
2de0e73284 Merge branch 'Dokploy:canary' into canary 2025-01-13 13:31:31 +07:00
shiqocred
537950dd9f Revision notification (#7)
* Update build-success.ts

* Update compose.ts

* Update application.ts

* Update notification.ts

* Update utils.ts

* Update dokploy-restart.ts

* Update docker-cleanup.ts

* Update database-backup.ts

* Update build-success.ts

* Update build-success.ts
2025-01-13 13:13:13 +07:00
Mauricio Siu
f2f3986c56 Merge pull request #1100 from Dokploy/canary
v0.17.1
2025-01-13 00:00:05 -06:00
Mauricio Siu
dd3fccea02 refactor: adjust sizes 2025-01-12 23:59:35 -06:00
Mauricio Siu
5052688aaf Merge pull request #1099 from Dokploy/fix/version
fix: adjust size of cards and add ssh keys
2025-01-12 23:52:58 -06:00
Mauricio Siu
5825b3eae7 Merge pull request #1098 from nktnet1/fix-ssh-keys-create-server
fix: query for ssh keys to show servers, rather than default empty
2025-01-12 23:52:28 -06:00
Mauricio Siu
dbca102178 fix: adjust size of cards and add ssh keys 2025-01-12 23:51:57 -06:00
Tam Nguyen
32a757a247 fix: query for ssh keys to show servers, rather than default empty 2025-01-13 16:44:34 +11:00
Mauricio Siu
8e4d8d68ca Merge pull request #1096 from Dokploy/canary
v0.17.0
2025-01-12 19:07:28 -06:00
Mauricio Siu
48e6c40a38 chore: bump version 2025-01-12 18:29:01 -06:00
Mauricio Siu
0a06c0aa28 refactor: add breadcrumb redis 2025-01-12 15:18:36 -06:00
Mauricio Siu
a965c0e924 fix: add prefix and remove resolution esm flag (#1095)
* fix: add prefix and remove resolution esm flag

* refactor: revert
2025-01-12 15:10:25 -06:00
Khiet Tam Nguyen
ae97595610 feat(template): added Actual Budget, a super fast and privacy-focused app for managing your finances (#1080)
Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
2025-01-12 14:46:11 -06:00
João Gabriel
106a660a2b fix: discord decoration tiny adjustment (#1086) 2025-01-12 14:42:39 -06:00
Alexis Loiseau
c25e7c53aa feat(template): added conduit, a matrix homeserver (#1087)
* feat(template): added conduit, a matrix homeserver

* Update apps/dokploy/templates/conduit/index.ts

* Update apps/dokploy/templates/conduit/index.ts

---------

Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
2025-01-12 14:41:50 -06:00
Vishal kadam
c9308aebc2 style: enhance template selection UI and add view modes toggle (#1094)
* feat: enhance template selection UI and add view modes toggle

* fix: show template tags only in detailed view mode

* refactor: set detailed

---------

Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
2025-01-12 14:34:28 -06:00
sao-coding
c0a2d2c399 feat(i18n): add missing keys and improve wording in Traditional Chinese translations (#1091)
* feat(i18n): add missing keys to Traditional Chinese translation

* feat(i18n): add missing keys to Traditional Chinese translation
2025-01-12 14:31:07 -06:00
Mauricio Siu
a104867ed2 Feat/add sidebar (#1084)
* refactor: add sidebar

* chore: add deps

* refactor: update sidebar

* refactor: another layout

* refactor: update variant

* refactor: change layout

* refactor: change variant

* refactor: enhance sidebar navigation with active state management

* feat: add project button to dashboard

* Merge branch 'canary' into feat/add-sidebar

* refactor: add loader

* refactor: update destinations and refactor

* refactor: ui refactor certificates

* refactor: delete unused files

* refactor: remove unused files and duplicate registry

* refactor: update style registry

* refactor: add new design registry

* refactor: enhance git providers

* refactor: remove duplicate files

* refactor: update

* refactor: update users

* refactor: delete unused files

* refactor: update profile

* refactor: apply changes

* refactor: update UI

* refactor: enhance Docker monitoring UI layout

* refactor: add theme toggle and language selection to user navigation (#1083)

* refactor: remove unused files

* feat: add filter to services

* refactor: add active items

* refactor: remove tab prop

* refactor: remove unused files

* refactor: remove duplicated files

* refactor: remove unused files

* refactor: remove duplicate files

* refactor: remove unused files

* refactor: delete unused files

* refactor: remove unsued files

* refactor: delete unused files

* refactor: lint

* refactor: remove unused secuirty

* refactor: delete unused files

* refactor: delete unused files

* remove imports

* refactor: add update button

* refactor: delete unused files

* refactor: remove unused code

* refactor: remove unused files

* refactor: update login page

* refactor: update login UI

* refactor: update ui reset password

* refactor: add justify end

* feat: add suscriptions

* feat: add sheet

* feat: add logs for postgres

* feat: add logs for all databases

* feat: add server logs with drawer logs

* refactor: remove unused files

* refactor: add refetch when closing

* refactor: fix linter

* chore: bump node-20

* revert

* refactor: fix conflicts

* refactor: update

* refactor: add missing deps

* refactor: delete duplicate files

* refactor: delete unsued files

* chore: lint

* refactor: remove unsued file

* refactor: add refetch

* refactor: remove duplicated files

* refactor: delete unused files

* refactor: update setup onboarding

* refactor: add breadcrumb

* refactor: apply updates

* refactor: add faker

* refactor: use 0 in validation

* refactor: show correct state

* refactor: update

---------

Co-authored-by: vishalkadam47 <vishal@jeevops.com>
Co-authored-by: Vishal kadam <107353260+vishalkadam47@users.noreply.github.com>
2025-01-12 14:29:43 -06:00
Tobias Barsnes
87f4c7b71b refactor: better focus-visible a11y (#1017)
* refactor: better focus-visible a11y

* style: fix tree leaf width

* style: input focus ring size

* refactor: focus a11y on project pages

* fix: project-environment import statement

* style: `ring-border` on input

* refactor: use ring border

---------

Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
2025-01-11 17:42:05 -06:00
shiqocred
d2094d6d76 Update notification.ts (#6)
fix router notification
2025-01-12 02:14:09 +07:00
shiqocred
c0b8a411bd Update build-success.ts (#5)
add format
2025-01-12 02:04:10 +07:00
shiqocred
1d8db07fa1 Add inline button telegram (#4)
* Update utils.ts

add type inline button

* Update dokploy-restart.ts

fixing format massage and adding [] for inline button type

* Update docker-cleanup.ts

fixing telegram message

* Update database-backup.ts

fixing telegram message

* Update build-error.ts

fixing message and adding button logs view

* Update build-success.ts

fixing message, adding domains props, adding inline button

* Update compose.ts

adding get domains compose and send to notif

* Update application.ts

adding get domains and send it to notif

* Update build-success.ts

fix space

* Update dokploy-restart.ts

fixing space
2025-01-12 01:20:39 +07:00
shiqocred
dd3618bfd9 Add inline button telegram (#3)
* Update utils.ts

add type inline button

* Update dokploy-restart.ts

fixing format massage and adding [] for inline button type

* Update docker-cleanup.ts

fixing telegram message

* Update database-backup.ts

fixing telegram message

* Update build-error.ts

fixing message and adding button logs view

* Update build-success.ts

fixing message, adding domains props, adding inline button

* Update compose.ts

adding get domains compose and send to notif

* Update application.ts

adding get domains and send it to notif
2025-01-12 00:55:31 +07:00
Khiet Tam Nguyen
9db979e43f fix(ui): full width for body tag (#1078) 2025-01-10 22:02:40 -06:00
shiqocred
553ae70656 feat(i18n): indonesian language (#1082)
* Update languages.ts

* Create common.json

* Create settings.json
2025-01-10 22:01:53 -06:00
Andrey Kucherenko
b58b6636e3 feat: add AI assistant to dokploy 2025-01-10 08:18:43 +01:00
Mauricio Siu
15fd663972 Merge pull request #1066 from szwabodev/fix/localServerTerminal
fix: connection to local server terminal
2025-01-10 00:45:11 -06:00
Mauricio Siu
123605dc0d Merge pull request #1068 from yiiman-dev/canary
Update wordpress version to latest
2025-01-08 22:20:38 -06:00
Mauricio Siu
d3f3265728 Merge pull request #1065 from nktnet1/gotenberg-template
feat(template): added Gotenberg, a Docker-powered stateless API for PDF files
2025-01-08 22:19:10 -06:00
Mauricio Siu
1a956314f8 Update apps/dokploy/templates/gotenberg/docker-compose.yml 2025-01-08 22:17:07 -06:00
Mauricio Siu
15cc8440f2 Merge pull request #1064 from nktnet1/template-generateBase64-import-index.ts
docs(contribution): fix template index.ts
2025-01-08 22:15:21 -06:00
Tam Nguyen
4a9d7225c9 chore: typo "accesed" changed to "accessed" for TS code 2025-01-08 13:44:59 +11:00
Saman Beheshtian (YiiMan)
103654d678 Update wordpress version to latest 2025-01-08 02:07:19 +00:00
Khiet Tam Nguyen
34a375776e docs: added the "tools" tag for gotenberg 2025-01-07 09:23:58 +11:00
Khiet Tam Nguyen
644fb1ef43 fix: switch to using gotenberg "latest" tag 2025-01-07 09:07:38 +11:00
Khiet Tam Nguyen
4e581bae99 fix: updated gotenberg from 8.15.2 -> 8.15.3 2025-01-07 09:03:49 +11:00
UndefinedPony
ee9f4796c3 style: remove spaces from commands 2025-01-06 12:12:23 +01:00
UndefinedPony
41a970c526 feat: add permission grant command when key generation fails 2025-01-06 12:09:54 +01:00
UndefinedPony
76c6d02566 feat: add ssh key comment for auto generated key 2025-01-06 12:09:15 +01:00
UndefinedPony
d0a5427c66 fix: connecting to local server terminal 2025-01-06 11:41:34 +01:00
UndefinedPony
aa3541b67b feat: add utils to get docker host and check if running on WSL 2025-01-06 11:39:19 +01:00
UndefinedPony
c31ed2b2b0 feat: add iproute2 as dependency to dockerfile 2025-01-06 11:38:08 +01:00
Khiet Tam Nguyen
a42b0ba32b docs(template): more concise description for gotenberg 2025-01-06 19:20:40 +11:00
Tam Nguyen
6866da97dd feat(template): added gotenberg, a pdf API service 2025-01-06 15:26:18 +11:00
Tam Nguyen
fdaea01b5b docs(contribution): added missing generateBase64 import for template index.ts 2025-01-06 14:00:43 +11:00
Khiet Tam Nguyen
fa2d81d2e7 docs(contribution): added missing generateBase64 import for template index.ts 2025-01-06 13:50:38 +11:00
Mauricio Siu
332416b7e7 Merge pull request #1054 from kerimovok/canary
feat(i18n): azerbaijani language
2025-01-05 15:34:17 -06:00
Mauricio Siu
7a4ee76eb6 Merge pull request #1039 from CyberHotline/canary
Added GLPI template
2025-01-05 14:50:09 -06:00
Mauricio Siu
0c304bd304 Merge pull request #1053 from SIPC/patch-1
Updated Chinese translation
2025-01-05 14:48:34 -06:00
Orkhan Karimov
8314c1a0a5 fix(indention): tab -> 4 spaces 2025-01-06 00:16:09 +04:00
Mauricio Siu
46a4e31e71 chore: add new sponsors 2025-01-03 16:19:48 -06:00
Mauricio Siu
acd3e0dd7d Merge pull request #1060 from jmsx/canary
fix: Update Vaultwarden version to 1.32.7 [CVE fix]
2025-01-03 14:58:18 -06:00
Mohab Gabber
f4e7a4d79a Merge branch 'canary' into canary 2025-01-03 19:11:33 +02:00
Jose Manuel Gonzalez
c7e5eba086 fix: Update Vaultwarden version to 1.32.7 [CVE fix] 2025-01-03 10:51:42 +01:00
Mauricio Siu
65361d18c2 Merge pull request #1055 from wish-oss/fix/console-error
refactor: remove unnecessary error logging in GPU setup functions
2025-01-02 16:21:04 -06:00
Mohab Gabber
bb5fd9895d fix: update MySQL image reference in docker-compose.yml 2025-01-02 21:28:52 +02:00
vishalkadam47
49a6b72c60 refactor: remove unnecessary error logging in GPU setup functions 2025-01-02 21:01:03 +05:30
Orkhan Karimov
84a88299ea feat(i18n): azerbaijani language 2025-01-02 16:27:57 +04:00
ink
d0fe635620 Update settings.json 2025-01-02 19:43:20 +08:00
Mauricio Siu
41d4ff8489 Merge pull request #1051 from wyattjoh/canary
fix: added unzip to server setup
2025-01-02 01:25:32 -06:00
Mauricio Siu
b9d8a48ae6 Merge pull request #1052 from Dokploy/1050-dokploy-ignore-run-command
refactor: add text for run command
2025-01-01 19:29:48 -06:00
Mauricio Siu
3e8708d2b9 refactor: add text for run command 2025-01-01 19:29:33 -06:00
Mauricio Siu
2b0232a24c Merge pull request #1047 from Pi-Bouf/environment-style-improvement
style: environment style improvement
2025-01-01 19:25:26 -06:00
Mauricio Siu
cbdea7cf48 Merge pull request #1049 from designorant/style/favicon-theme
style: enhance favicon with dark mode support
2025-01-01 19:19:56 -06:00
Mauricio Siu
f042cb720f Merge pull request #1042 from szwabodev/feat/newUpdateModalInNavbar
feat: use check updates modal for update available in navbar
2025-01-01 19:15:12 -06:00
Mauricio Siu
02b977bfc4 Merge pull request #1037 from 190km/feat-shows-req-when-is-active
feat: shows req when is active
2025-01-01 19:05:47 -06:00
Mauricio Siu
9f24f24de3 refactor: improve requests 2025-01-01 19:05:06 -06:00
Mauricio Siu
cbc8c24985 Merge pull request #1038 from 190km/swarm-style-improvement
style: swarm style improvement
2025-01-01 19:02:57 -06:00
Mauricio Siu
b17369264c refactor: remove min-h-screen 2025-01-01 19:02:24 -06:00
Mauricio Siu
9c783177c8 Merge pull request #1033 from DJKnaeckebrot/feature/check-mate-template
feat(template): add checkmate template
2025-01-01 18:57:32 -06:00
Mauricio Siu
f2159a3439 Update apps/dokploy/templates/checkmate/docker-compose.yml 2025-01-01 18:57:27 -06:00
Mauricio Siu
eec0a55212 Update apps/dokploy/templates/checkmate/docker-compose.yml 2025-01-01 18:57:23 -06:00
Mauricio Siu
16bab629de Merge pull request #1030 from DJKnaeckebrot/feature/fix-2fa-style
style(2fa): make pin input centered and make boarder more white to make it more visible
2025-01-01 18:54:49 -06:00
Mauricio Siu
592bf9292e Merge pull request #1032 from DJKnaeckebrot/feature/fix-huly-template
fix(huly-template): resolve issue with wrong domain
2025-01-01 18:54:33 -06:00
Mauricio Siu
b93d26f937 refactor: use shadcn ui classes 2025-01-01 18:53:30 -06:00
Mauricio Siu
c0e7f4ad8c Merge pull request #1029 from wish-oss/refactor/port-mapping-ui
refactor: enhance ManageTraefikPorts with ScrollArea for better UI
2025-01-01 18:52:09 -06:00
Mauricio Siu
05cf51bfb3 Merge pull request #1028 from designorant/feat/complete-polish
feat(i18n): add missing keys to Polish translation
2025-01-01 18:48:10 -06:00
Wyatt Johnson
7cf5cb4032 fix: added unzip to server setup
The installation script for rclone requires unzip to be present
in order to install. This adds this dep to the deps installed
as it's not present on Debian.
2025-01-01 16:44:41 -07:00
Michał Ordon
76787b9056 style: enhance favicon with dark mode support 2025-01-01 17:46:39 +00:00
Pierre B
513b17105e style: environment style improvement 2025-01-01 15:47:47 +01:00
UndefinedPony
187f051484 fix: check health endpoint instead of awaiting service restart to fix update success status 2024-12-31 11:20:59 +01:00
UndefinedPony
2be79304fb refactor: allow using already fetched updateData, remove ping icon 2024-12-31 11:19:22 +01:00
UndefinedPony
29a8cb63c0 refactor: pass update data and use updates modal in navbar 2024-12-31 11:13:58 +01:00
UndefinedPony
ed5936ede7 feat: add health endpoint for frontend app 2024-12-31 11:11:33 +01:00
Mohab Gabber
d4f89425db Added GLPI template 2024-12-30 23:45:59 +02:00
190km
a7983f32a6 style: added gap for cards title & gap for tls status & availability 2024-12-30 20:05:44 +01:00
190km
198ace236b feat: shows req when is active 2024-12-30 19:23:31 +01:00
djknaeckebrot
931f3fc28e feat: add checkmate template 2024-12-30 16:08:04 +01:00
DJKnaeckebrot
a4cc3b619a fix: resolve issue with wrong domain 2024-12-30 11:07:28 +01:00
DJKnaeckebrot
cb5077cfcc style(2fa): make pin input centered and make boarder more white to make it more visible 2024-12-30 10:54:47 +01:00
vishalkadam47
9122a1e4b2 refactor: enhance ManageTraefikPorts with ScrollArea for better UI and port management display 2024-12-30 07:32:34 +05:30
Michał Ordon
b2cf442d9b feat(i18n): add missing keys to Polish translation 2024-12-30 01:52:09 +00:00
Mauricio Siu
973b54408e Merge pull request #1027 from Dokploy/canary
v0.16.1
2024-12-29 18:39:17 -06:00
Mauricio Siu
638fbe17a6 refactor: update paths 2024-12-29 18:23:07 -06:00
Mauricio Siu
75e0d0ba78 refactor: add is cloud validation 2024-12-29 18:14:32 -06:00
Mauricio Siu
0f1b911236 chore: bump version 2024-12-29 18:08:20 -06:00
Mauricio Siu
da148a6c50 Merge pull request #959 from szwabodev/feat/localServerTerminal
feat: local server terminal access
2024-12-29 18:06:32 -06:00
Mauricio Siu
c168648cce Merge pull request #1026 from 190km/fix/missing-norwegian-select
fix: missing norwegian lang in select
2024-12-29 17:40:16 -06:00
190km
efd3ad4102 fix: missing norwegian lang in select 2024-12-30 00:30:57 +01:00
Mauricio Siu
a9577395eb Merge pull request #1025 from Dokploy/998-add-unifi-applicaton
feat: add unifi template
2024-12-29 17:19:33 -06:00
Mauricio Siu
1d02d4308f feat: add unifi template 2024-12-29 17:19:17 -06:00
Mauricio Siu
e93e15a9c8 Merge pull request #995 from 190km/refactor-i18n
feat: i18n displays real lang name
2024-12-29 16:14:03 -06:00
Mauricio Siu
511a9beaa5 Merge pull request #1024 from Dokploy/fix/add-fallback-getIp
refactor: add fallback ipv4
2024-12-29 16:13:48 -06:00
Mauricio Siu
ac8c180ba6 refactor: add fallback ipv4 2024-12-29 16:13:06 -06:00
Mauricio Siu
9a2898ba4f Merge pull request #1023 from Dokploy/1008-s3-provider-dropdown-default-value-reset
fix: add provider value in edit destination
2024-12-29 15:56:50 -06:00
Mauricio Siu
2c258c84eb fix: add provider value in edit destination 2024-12-29 15:56:30 -06:00
Mauricio Siu
36511f34f8 Merge branch 'canary' into refactor-i18n 2024-12-29 15:51:17 -06:00
Mauricio Siu
fee5bbc535 Merge pull request #1022 from Dokploy/fix/cron-jobs
fix: add try catch inside of cron jobs
2024-12-29 15:49:28 -06:00
Mauricio Siu
84ad9a5bb3 refactor: revert notifications 2024-12-29 15:45:54 -06:00
Mauricio Siu
69576988eb Merge pull request #1021 from Dokploy/fix/memory-reservation
Fix/memory reservation
2024-12-29 15:36:14 -06:00
Mauricio Siu
d65d050494 fix: add try catch inside of cron jobs 2024-12-29 15:35:15 -06:00
Mauricio Siu
46a5adf793 refactor: remove muted text 2024-12-29 14:17:15 -06:00
Mauricio Siu
5558ee3248 fix: add tooltip and placeholder values 2024-12-29 14:10:41 -06:00
Mauricio Siu
5d8ebd027e fix: parse correctly numbers 2024-12-29 13:53:38 -06:00
Mauricio Siu
9aa49ce9be Merge pull request #1019 from jocubeit/canary
Spelling and grammar corrections
2024-12-29 13:52:31 -06:00
Mauricio Siu
7bcfc17fe9 Merge pull request #1020 from champ3oy/patch-1
convert email to lower case when loging in
2024-12-29 13:34:27 -06:00
cirlorm
5d2d4104f0 convert email to lower case when loging in
I had an issue signing in to my dashboard only to find out it was because the first letter of my email was in caps.
2024-12-29 17:46:44 +00:00
Dom
2af8d6f565 Spelling and grammar corrections 2024-12-29 22:34:57 +10:00
Mauricio Siu
ba45b27608 Merge pull request #1010 from joaotonaco/canary
feat: prevent layout shifting from scrollbar
2024-12-28 19:31:36 -06:00
Mauricio Siu
5aa1c6efb7 Merge pull request #1011 from joaotonaco/fix/inconsistent-service-card-height
fix: inconsistent service card height
2024-12-28 19:30:42 -06:00
Mauricio Siu
e9dad8f9f8 Merge pull request #1016 from 190km/style/swarm
feat: swarm overview style
2024-12-28 19:30:13 -06:00
Mauricio Siu
0c2a382541 Update apps/dokploy/components/dashboard/settings/servers/show-swarm-overview-modal.tsx 2024-12-28 19:29:51 -06:00
190km
81408985d4 feat: swarm overview style 2024-12-29 01:07:47 +01:00
João Gabriel
d45b558251 fix: inconsistent service card height 2024-12-27 11:08:55 -03:00
João Gabriel
a30b1a0cb8 refactor: prevent layout shifting from scrollbar 2024-12-27 10:51:31 -03:00
João Gabriel
c946e3e01b refactor: prevent layout shifting from scrollbar 2024-12-27 10:50:36 -03:00
190km
f242f5c65e chore: lint 2024-12-26 01:29:18 +01:00
190km
46348f43f6 chore: lint 2024-12-26 01:26:53 +01:00
190km
c3476a1fdf refactor: refactored i18n to display real lang name 2024-12-26 00:04:50 +01:00
UndefinedPony
228d12a61c refactor: code cleanup 2024-12-22 19:01:02 +01:00
UndefinedPony
fd0a9b8b58 feat: add local server config, add local server support in terminal modal, add fixed terminal container height 2024-12-22 18:46:02 +01:00
UndefinedPony
1ea7d2e1bf feat: enable server translations for servers page 2024-12-22 18:44:38 +01:00
UndefinedPony
7e08c8881e refactor: adapt terminal component to pass local server data and add initialized check 2024-12-22 16:37:26 +01:00
UndefinedPony
e68d867d31 refactor: adapt terminal wss to allow local server connection, add status logs 2024-12-22 16:35:28 +01:00
UndefinedPony
a53929a787 feat: add enter terminal option to server dropdown 2024-12-22 16:32:20 +01:00
UndefinedPony
ec8eaf6249 feat: add util to configure and get private ssh key for local ssh connection 2024-12-22 16:31:52 +01:00
UndefinedPony
0d58935ad1 refactor: translations for common terminal stuff 2024-12-22 16:30:53 +01:00
UndefinedPony
e2b155280a feat: add enter terminal translation 2024-12-21 20:08:08 +01:00
Mauricio Siu
79b733536f Merge branch 'canary' into feat/stack-env-support 2024-12-08 18:35:40 -06:00
xenonwellz
65ee0a3e22 fix(builder): created processed file in the same directory as main stack.yml 2024-11-11 02:22:48 +01:00
xenonwellz
c9b570e469 fix(builder): fixed issues on non-raw compose and external servers 2024-11-09 13:00:15 +01:00
xenonwellz
dafed3096f refactor(builder): removed path log 2024-11-04 22:47:58 +01:00
xenonwellz
f772fec407 fix(bundler): docker-compose bug 2024-11-04 11:02:46 +01:00
xenonwellz
06cbd1fce1 refactor(bundler): removed redundant code 2024-11-03 15:26:54 +01:00
xenonwellz
9c355bcfb7 refactor(builder): removed unused and redundant code 2024-11-02 12:42:53 +01:00
xenonwellz
06081627e8 refactor: used docker stack config 2024-11-02 00:35:38 +01:00
xenonwellz
dc1e12d6ed feat(compose): added stop functionality for stack 2024-11-01 23:35:42 +01:00
xenonwellz
cb02deb837 fix(builder): fixed docker-compose issue 2024-11-01 23:15:12 +01:00
xenonwellz
94786c738b feat: added env support for dokploy 2024-11-01 15:27:00 +01:00
988 changed files with 168374 additions and 42937 deletions

View File

@@ -1,119 +0,0 @@
version: 2.1
jobs:
build-amd64:
machine:
image: ubuntu-2004:current
steps:
- checkout
- run:
name: Prepare .env file
command: |
cp apps/dokploy/.env.production.example .env.production
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
- run:
name: Build and push AMD64 image
command: |
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
if [ "${CIRCLE_BRANCH}" == "main" ]; then
TAG="latest"
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
TAG="canary"
else
TAG="feature"
fi
docker build --platform linux/amd64 -t dokploy/dokploy:${TAG}-amd64 .
docker push dokploy/dokploy:${TAG}-amd64
build-arm64:
machine:
image: ubuntu-2004:current
resource_class: arm.large
steps:
- checkout
- run:
name: Prepare .env file
command: |
cp apps/dokploy/.env.production.example .env.production
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
- run:
name: Build and push ARM64 image
command: |
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
if [ "${CIRCLE_BRANCH}" == "main" ]; then
TAG="latest"
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
TAG="canary"
else
TAG="feature"
fi
docker build --platform linux/arm64 -t dokploy/dokploy:${TAG}-arm64 .
docker push dokploy/dokploy:${TAG}-arm64
combine-manifests:
docker:
- image: cimg/node:18.18.0
steps:
- checkout
- setup_remote_docker
- run:
name: Create and push multi-arch manifest
command: |
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
if [ "${CIRCLE_BRANCH}" == "main" ]; then
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
echo $VERSION
TAG="latest"
docker manifest create dokploy/dokploy:${TAG} \
dokploy/dokploy:${TAG}-amd64 \
dokploy/dokploy:${TAG}-arm64
docker manifest push dokploy/dokploy:${TAG}
docker manifest create dokploy/dokploy:${VERSION} \
dokploy/dokploy:${TAG}-amd64 \
dokploy/dokploy:${TAG}-arm64
docker manifest push dokploy/dokploy:${VERSION}
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
TAG="canary"
docker manifest create dokploy/dokploy:${TAG} \
dokploy/dokploy:${TAG}-amd64 \
dokploy/dokploy:${TAG}-arm64
docker manifest push dokploy/dokploy:${TAG}
else
TAG="feature"
docker manifest create dokploy/dokploy:${TAG} \
dokploy/dokploy:${TAG}-amd64 \
dokploy/dokploy:${TAG}-arm64
docker manifest push dokploy/dokploy:${TAG}
fi
workflows:
build-all:
jobs:
- build-amd64:
filters:
branches:
only:
- main
- canary
- fix/nixpacks-version
- build-arm64:
filters:
branches:
only:
- main
- canary
- fix/nixpacks-version
- combine-manifests:
requires:
- build-amd64
- build-arm64
filters:
branches:
only:
- main
- canary
- fix/nixpacks-version

View File

@@ -1,6 +1,6 @@
name: Bug Report
description: Create a bug report
labels: ["bug"]
labels: ["needs-triage🔍"]
body:
- type: markdown
attributes:
@@ -62,6 +62,7 @@ body:
- "Docker"
- "Remote server"
- "Local Development"
- "Cloud Version"
validations:
required: true
- type: dropdown

BIN
.github/sponsors/its.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
.github/sponsors/light-node.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
.github/sponsors/openalternative.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
.github/sponsors/synexa.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

83
.github/workflows/create-pr.yml vendored Normal file
View File

@@ -0,0 +1,83 @@
name: Auto PR to main when version changes
on:
push:
branches:
- canary
permissions:
contents: write
pull-requests: write
jobs:
create-pr:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version from package.json
id: package_version
run: echo "VERSION=$(jq -r .version ./apps/dokploy/package.json)" >> $GITHUB_ENV
- name: Get latest GitHub tag
id: latest_tag
run: |
LATEST_TAG=$(git ls-remote --tags origin | awk -F'/' '{print $3}' | sort -V | tail -n1)
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
echo $LATEST_TAG
- name: Compare versions
id: compare_versions
run: |
if [ "${{ env.VERSION }}" != "${{ env.LATEST_TAG }}" ]; then
VERSION_CHANGED="true"
else
VERSION_CHANGED="false"
fi
echo "VERSION_CHANGED=$VERSION_CHANGED" >> $GITHUB_ENV
echo "Comparing versions:"
echo "Current version: ${{ env.VERSION }}"
echo "Latest tag: ${{ env.LATEST_TAG }}"
echo "Version changed: $VERSION_CHANGED"
- name: Check if a PR already exists
id: check_pr
run: |
PR_EXISTS=$(gh pr list --state open --base main --head canary --json number --jq '. | length')
echo "PR_EXISTS=$PR_EXISTS" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
- name: Create Pull Request
if: env.VERSION_CHANGED == 'true' && env.PR_EXISTS == '0'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git fetch origin main
git checkout canary
git push origin canary
gh pr create \
--title "🚀 Release ${{ env.VERSION }}" \
--body '
This PR promotes changes from `canary` to `main` for version ${{ env.VERSION }}.
### 🔍 Changes Include:
- Version bump to ${{ env.VERSION }}
- All changes from canary branch
### ✅ Pre-merge Checklist:
- [ ] All tests passing
- [ ] Documentation updated
- [ ] Docker images built and tested
> 🤖 This PR was automatically generated by [GitHub Actions](https://github.com/actions)' \
--base main \
--head canary \
--label "release" --label "automated pr" || true \
--reviewer siumauricio \
--assignee siumauricio
env:
GH_TOKEN: ${{ github.token }}

View File

@@ -2,7 +2,7 @@ name: Build Docker images
on:
push:
branches: ["canary", "main"]
branches: ["canary", "main", "feat/monitoring"]
jobs:
build-and-push-cloud-image:
@@ -17,7 +17,7 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
@@ -53,8 +53,7 @@ jobs:
push: true
tags: |
siumauricio/schedule:${{ github.ref_name == 'main' && 'latest' || 'canary' }}
platforms: linux/amd64
platforms: linux/amd64
build-and-push-server-image:
runs-on: ubuntu-latest
@@ -77,4 +76,4 @@ jobs:
push: true
tags: |
siumauricio/server:${{ github.ref_name == 'main' && 'latest' || 'canary' }}
platforms: linux/amd64
platforms: linux/amd64

161
.github/workflows/dokploy.yml vendored Normal file
View File

@@ -0,0 +1,161 @@
name: Dokploy Docker Build
on:
push:
branches: [main, canary, "feat/better-auth-2"]
env:
IMAGE_NAME: dokploy/dokploy
jobs:
docker-amd:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set tag and version
id: meta
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
TAG="latest"
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then
TAG="canary"
else
TAG="feature"
fi
echo "tags=${IMAGE_NAME}:${TAG}-amd64" >> $GITHUB_OUTPUT
- name: Prepare env file
run: |
cp apps/dokploy/.env.production.example .env.production
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
docker-arm:
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set tag and version
id: meta
run: |
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
TAG="latest"
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then
TAG="canary"
else
TAG="feature"
fi
echo "tags=${IMAGE_NAME}:${TAG}-arm64" >> $GITHUB_OUTPUT
- name: Prepare env file
run: |
cp apps/dokploy/.env.production.example .env.production
cp apps/dokploy/.env.production.example apps/dokploy/.env.production
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
combine-manifests:
needs: [docker-amd, docker-arm]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create and push manifests
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
TAG="latest"
docker buildx imagetools create -t ${IMAGE_NAME}:${TAG} \
${IMAGE_NAME}:${TAG}-amd64 \
${IMAGE_NAME}:${TAG}-arm64
docker buildx imagetools create -t ${IMAGE_NAME}:${VERSION} \
${IMAGE_NAME}:${TAG}-amd64 \
${IMAGE_NAME}:${TAG}-arm64
elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then
TAG="canary"
docker buildx imagetools create -t ${IMAGE_NAME}:${TAG} \
${IMAGE_NAME}:${TAG}-amd64 \
${IMAGE_NAME}:${TAG}-arm64
else
TAG="feature"
docker buildx imagetools create -t ${IMAGE_NAME}:${TAG} \
${IMAGE_NAME}:${TAG}-amd64 \
${IMAGE_NAME}:${TAG}-arm64
fi
generate-release:
needs: [combine-manifests]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version
id: get_version
run: |
VERSION=$(node -p "require('./apps/dokploy/package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.get_version.outputs.version }}
name: ${{ steps.get_version.outputs.version }}
generate_release_notes: true
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

118
.github/workflows/monitoring.yml vendored Normal file
View File

@@ -0,0 +1,118 @@
name: Dokploy Monitoring Build
on:
push:
branches: [main, canary]
env:
IMAGE_NAME: dokploy/monitoring
jobs:
docker-amd:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set tag
id: meta
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
TAG="latest"
elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then
TAG="canary"
else
TAG="feature"
fi
echo "tags=${IMAGE_NAME}:${TAG}-amd64" >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.monitoring
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
docker-arm:
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set
id: meta
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
TAG="latest"
elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then
TAG="canary"
else
TAG="feature"
fi
echo "tags=${IMAGE_NAME}:${TAG}-arm64" >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.monitoring
platforms: linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
combine-manifests:
needs: [docker-amd, docker-arm]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create and push manifests
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
TAG="latest"
docker buildx imagetools create -t ${IMAGE_NAME}:${TAG} \
${IMAGE_NAME}:${TAG}-amd64 \
${IMAGE_NAME}:${TAG}-arm64
elif [ "${{ github.ref }}" = "refs/heads/canary" ]; then
TAG="canary"
docker buildx imagetools create -t ${IMAGE_NAME}:${TAG} \
${IMAGE_NAME}:${TAG}-amd64 \
${IMAGE_NAME}:${TAG}-arm64
else
TAG="feature"
docker buildx imagetools create -t ${IMAGE_NAME}:${TAG} \
${IMAGE_NAME}:${TAG}-amd64 \
${IMAGE_NAME}:${TAG}-arm64
fi

View File

@@ -12,7 +12,7 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18.18.0
node-version: 20.9.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build
@@ -26,7 +26,7 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18.18.0
node-version: 20.9.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build
@@ -39,7 +39,7 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18.18.0
node-version: 20.9.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build

3
.gitignore vendored
View File

@@ -39,3 +39,6 @@ yarn-error.log*
# Misc
.DS_Store
*.pem
.db

2
.nvmrc
View File

@@ -1 +1 @@
18.18.0
20.9.0

View File

@@ -14,12 +14,10 @@ We have a few guidelines to follow when contributing to this project:
## Commit Convention
Before you create a Pull Request, please make sure your commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
### Commit Message Format
```
<type>[optional scope]: <description>
@@ -54,6 +52,8 @@ feat: add new feature
Before you start, please make the clone based on the `canary` branch, since the `main` branch is the source of truth and should always reflect the latest stable release, also the PRs will be merged to the `canary` branch.
We use Node v20.9.0
```bash
git clone https://github.com/dokploy/dokploy.git
cd dokploy
@@ -73,9 +73,10 @@ Run the command that will spin up all the required services and files.
pnpm run dokploy:setup
```
Run this script
Run this script
```bash
pnpm run server:script
pnpm run server:script
```
Now run the development server.
@@ -137,11 +138,18 @@ curl -sSL https://nixpacks.com/install.sh -o install.sh \
&& ./install.sh
```
```bash
# Install Railpack
curl -sSL https://railpack.com/install.sh | sh
```
```bash
# Install Buildpacks
curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
```
## Pull Request
- The `main` branch is the source of truth and should always reflect the latest stable release.
@@ -169,6 +177,7 @@ Let's take the example of `plausible` template.
```typescript
// EXAMPLE
import {
generateBase64,
generateHash,
generateRandomDomain,
type Template,
@@ -200,8 +209,8 @@ export function generate(schema: Schema): Template {
const mounts: Template["mounts"] = [
{
mountPath: "./clickhouse/clickhouse-config.xml",
content: `some content......`,
filePath: "./clickhouse/clickhouse-config.xml",
content: "some content......",
},
];
@@ -247,4 +256,3 @@ export function generate(schema: Schema): Template {
## Docs & Website
To contribute to the Dokploy docs or website, please go to this [repository](https://github.com/Dokploy/website).

View File

@@ -1,4 +1,4 @@
FROM node:18-slim AS base
FROM node:20.9-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
@@ -7,7 +7,7 @@ FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y python3 make g++ git python3-pip pkg-config libsecret-1-dev && rm -rf /var/lib/apt/lists/*
# Install dependencies
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
@@ -29,7 +29,7 @@ WORKDIR /app
# Set production
ENV NODE_ENV=production
RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y curl unzip apache2-utils iproute2 && rm -rf /var/lib/apt/lists/*
# Copy only the necessary files
COPY --from=build /prod/dokploy/.next ./.next
@@ -55,6 +55,10 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
&& ./install.sh \
&& pnpm install -g tsx
# Install Railpack
ARG RAILPACK_VERSION=0.0.37
RUN curl -sSL https://railpack.com/install.sh | bash
# Install buildpacks
COPY --from=buildpacksio/pack:0.35.0 /usr/local/bin/pack /usr/local/bin/pack

View File

@@ -1,4 +1,4 @@
FROM node:18-slim AS base
FROM node:20.9-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
@@ -7,7 +7,7 @@ FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y python3 make g++ git python3-pip pkg-config libsecret-1-dev && rm -rf /var/lib/apt/lists/*
# Install dependencies
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/dokploy install --frozen-lockfile

41
Dockerfile.monitoring Normal file
View File

@@ -0,0 +1,41 @@
# Build stage
FROM golang:1.21-alpine3.19 AS builder
# Instalar dependencias necesarias
RUN apk add --no-cache gcc musl-dev sqlite-dev
# Establecer el directorio de trabajo
WORKDIR /app
# Copiar todo el código fuente primero
COPY . .
# Movernos al directorio de la aplicación golang
WORKDIR /app/apps/monitoring
# Descargar dependencias
RUN go mod download
# Compilar la aplicación
RUN CGO_ENABLED=1 GOOS=linux go build -o main main.go
# Etapa final
FROM alpine:3.19
# Instalar SQLite y otras dependencias necesarias
RUN apk add --no-cache sqlite-libs docker-cli
WORKDIR /app
# Copiar el binario compilado y el archivo monitor.go
COPY --from=builder /app/apps/monitoring/main ./main
COPY --from=builder /app/apps/monitoring/main.go ./monitor.go
# COPY --from=builder /app/apps/golang/.env ./.env
# Exponer el puerto
ENV PORT=3001
EXPOSE 3001
# Ejecutar la aplicación
CMD ["./main"]

View File

@@ -1,4 +1,4 @@
FROM node:18-slim AS base
FROM node:20.9-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
@@ -7,7 +7,7 @@ FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y python3 make g++ git python3-pip pkg-config libsecret-1-dev && rm -rf /var/lib/apt/lists/*
# Install dependencies
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/schedules install --frozen-lockfile

View File

@@ -1,4 +1,4 @@
FROM node:18-slim AS base
FROM node:20.9-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
@@ -7,7 +7,7 @@ FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y python3 make g++ git python3-pip pkg-config libsecret-1-dev && rm -rf /var/lib/apt/lists/*
# Install dependencies
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/api install --frozen-lockfile

View File

@@ -71,6 +71,11 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
<a href="https://mandarin3d.com/?ref=dokploy" target="_blank" style="display: inline-block;">
<img src=".github/sponsors/mandarin.png" alt="Mandarin" height="50"/>
</a>
<a href="https://lightnode.com/?ref=dokploy" target="_blank" style="display: inline-block;">
<img src=".github/sponsors/light-node.webp" alt="Lightnode" height="70"/>
</a>
</div>
### Premium Supporters 🥇
@@ -89,8 +94,12 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
<a href="https://lightspeed.run/?ref=dokploy"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://cloudblast.io/?ref=dokploy "><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Cloudblast.io"/></a>
<a href="https://startupfa.me/?ref=dokploy "><img src=".github/sponsors/startupfame.png" width="65px" alt="Startupfame"/></a>
<a href="https://itsdb-center.com?ref=dokploy "><img src=".github/sponsors/its.png" width="65px" alt="Itsdb-center"/></a>
<a href="https://openalternative.co/?ref=dokploy "><img src=".github/sponsors/openalternative.png" width="65px" alt="Openalternative"/></a>
<a href="https://synexa.ai/?ref=dokploy"><img src=".github/sponsors/synexa.png" width="65px" alt="Synexa"/></a>
</div>
### Community Backers 🤝
<div style="display: flex; gap: 30px; flex-wrap: wrap;">

View File

@@ -5,7 +5,7 @@
"scripts": {
"dev": "PORT=4000 tsx watch src/index.ts",
"build": "tsc --project tsconfig.json",
"start": "node --experimental-specifier-resolution=node dist/index.js",
"start": "node dist/index.js",
"typecheck": "tsc --noEmit"
},
"dependencies": {

View File

@@ -4,9 +4,9 @@ import "dotenv/config";
import { zValidator } from "@hono/zod-validator";
import { Queue } from "@nerimity/mimiqueue";
import { createClient } from "redis";
import { logger } from "./logger";
import { type DeployJob, deployJobSchema } from "./schema";
import { deploy } from "./utils";
import { logger } from "./logger.js";
import { type DeployJob, deployJobSchema } from "./schema.js";
import { deploy } from "./utils.js";
const app = new Hono();
const redisClient = createClient({
@@ -28,7 +28,7 @@ app.use(async (c, next) => {
app.post("/deploy", zValidator("json", deployJobSchema), (c) => {
const data = c.req.valid("json");
const res = queue.add(data, { groupName: data.serverId });
queue.add(data, { groupName: data.serverId });
return c.json(
{
message: "Deployment Added",

View File

@@ -64,7 +64,7 @@ export const deploy = async (job: DeployJob) => {
}
}
}
} catch (error) {
} catch (_) {
if (job.applicationType === "application") {
await updateApplicationStatus(job.applicationId, "error");
} else if (job.applicationType === "compose") {

View File

@@ -1 +1 @@
18.18.0
20.9.0

View File

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

View File

@@ -9,6 +9,7 @@ describe("createDomainLabels", () => {
port: 8080,
https: false,
uniqueConfigKey: 1,
customCertResolver: null,
certificateType: "none",
applicationId: "",
composeId: "",

View File

@@ -293,29 +293,6 @@ networks:
dokploy-network:
`;
const expectedComposeFile7 = `
version: "3.8"
services:
web:
image: nginx:latest
networks:
- dokploy-network
networks:
dokploy-network:
driver: bridge
driver_opts:
com.docker.network.driver.mtu: 1200
backend:
driver: bridge
attachable: true
external_network:
external: true
name: dokploy-network
`;
test("It shoudn't add suffix to dokploy-network", () => {
const composeData = load(composeFile7) as ComposeSpecification;

View File

@@ -1,7 +1,7 @@
import { generateRandomHash } from "@dokploy/server";
import { addSuffixToSecretsRoot } from "@dokploy/server";
import type { ComposeSpecification } from "@dokploy/server";
import { dump, load } from "js-yaml";
import { load } from "js-yaml";
import { expect, test } from "vitest";
test("Generate random hash with 8 characters", () => {

View File

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

View File

@@ -0,0 +1,98 @@
import { extractCommitMessage } from "@/pages/api/deploy/[refreshToken]";
import { describe, expect, it } from "vitest";
describe("GitHub Webhook Skip CI", () => {
const mockGithubHeaders = {
"x-github-event": "push",
};
const createMockBody = (message: string) => ({
head_commit: {
message,
},
});
const skipKeywords = [
"[skip ci]",
"[ci skip]",
"[no ci]",
"[skip actions]",
"[actions skip]",
];
it("should detect skip keywords in commit message", () => {
for (const keyword of skipKeywords) {
const message = `feat: add new feature ${keyword}`;
const commitMessage = extractCommitMessage(
mockGithubHeaders,
createMockBody(message),
);
expect(commitMessage.includes(keyword)).toBe(true);
}
});
it("should not detect skip keywords in normal commit message", () => {
const message = "feat: add new feature";
const commitMessage = extractCommitMessage(
mockGithubHeaders,
createMockBody(message),
);
for (const keyword of skipKeywords) {
expect(commitMessage.includes(keyword)).toBe(false);
}
});
it("should handle different webhook sources", () => {
// GitHub
expect(
extractCommitMessage(
{ "x-github-event": "push" },
{ head_commit: { message: "[skip ci] test" } },
),
).toBe("[skip ci] test");
// GitLab
expect(
extractCommitMessage(
{ "x-gitlab-event": "push" },
{ commits: [{ message: "[skip ci] test" }] },
),
).toBe("[skip ci] test");
// Bitbucket
expect(
extractCommitMessage(
{ "x-event-key": "repo:push" },
{
push: {
changes: [{ new: { target: { message: "[skip ci] test" } } }],
},
},
),
).toBe("[skip ci] test");
// Gitea
expect(
extractCommitMessage(
{ "x-gitea-event": "push" },
{ commits: [{ message: "[skip ci] test" }] },
),
).toBe("[skip ci] test");
});
it("should handle missing commit message", () => {
expect(extractCommitMessage(mockGithubHeaders, {})).toBe("NEW COMMIT");
expect(extractCommitMessage({ "x-gitlab-event": "push" }, {})).toBe(
"NEW COMMIT",
);
expect(
extractCommitMessage(
{ "x-event-key": "repo:push" },
{ push: { changes: [] } },
),
).toBe("NEW COMMIT");
expect(extractCommitMessage({ "x-gitea-event": "push" }, {})).toBe(
"NEW COMMIT",
);
});
});

View File

@@ -27,6 +27,7 @@ if (typeof window === "undefined") {
const baseApp: ApplicationNested = {
applicationId: "",
herokuVersion: "",
watchPaths: [],
applicationStatus: "done",
appName: "",
autoDeploy: true,
@@ -37,6 +38,7 @@ const baseApp: ApplicationNested = {
isPreviewDeploymentsActive: false,
previewBuildArgs: null,
previewCertificateType: "none",
previewCustomCertResolver: null,
previewEnv: null,
previewHttps: false,
previewPath: "/",
@@ -45,7 +47,7 @@ const baseApp: ApplicationNested = {
previewWildcard: "",
project: {
env: "",
adminId: "",
organizationId: "",
name: "",
description: "",
createdAt: "",

View File

@@ -0,0 +1,425 @@
import { describe, expect, it } from "vitest";
import type { CompleteTemplate } from "@dokploy/server/templates/processors";
import { processTemplate } from "@dokploy/server/templates/processors";
import type { Schema } from "@dokploy/server/templates";
describe("processTemplate", () => {
// Mock schema for testing
const mockSchema: Schema = {
projectName: "test",
serverIp: "127.0.0.1",
};
describe("variables processing", () => {
it("should process basic variables with utility functions", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {
main_domain: "${domain}",
secret_base: "${base64:64}",
totp_key: "${base64:32}",
password: "${password:32}",
hash: "${hash:16}",
},
config: {
domains: [],
env: {},
},
};
const result = processTemplate(template, mockSchema);
expect(result.envs).toHaveLength(0);
expect(result.domains).toHaveLength(0);
expect(result.mounts).toHaveLength(0);
});
it("should allow referencing variables in other variables", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {
main_domain: "${domain}",
api_domain: "api.${main_domain}",
},
config: {
domains: [],
env: {},
},
};
const result = processTemplate(template, mockSchema);
expect(result.envs).toHaveLength(0);
expect(result.domains).toHaveLength(0);
expect(result.mounts).toHaveLength(0);
});
});
describe("domains processing", () => {
it("should process domains with explicit host", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {
main_domain: "${domain}",
},
config: {
domains: [
{
serviceName: "plausible",
port: 8000,
host: "${main_domain}",
},
],
env: {},
},
};
const result = processTemplate(template, mockSchema);
expect(result.domains).toHaveLength(1);
const domain = result.domains[0];
expect(domain).toBeDefined();
if (!domain) return;
expect(domain).toMatchObject({
serviceName: "plausible",
port: 8000,
});
expect(domain.host).toBeDefined();
expect(domain.host).toContain(mockSchema.projectName);
});
it("should generate random domain if host is not specified", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {},
config: {
domains: [
{
serviceName: "plausible",
port: 8000,
},
],
env: {},
},
};
const result = processTemplate(template, mockSchema);
expect(result.domains).toHaveLength(1);
const domain = result.domains[0];
expect(domain).toBeDefined();
if (!domain || !domain.host) return;
expect(domain.host).toBeDefined();
expect(domain.host).toContain(mockSchema.projectName);
});
it("should allow using ${domain} directly in host", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {},
config: {
domains: [
{
serviceName: "plausible",
port: 8000,
host: "${domain}",
},
],
env: {},
},
};
const result = processTemplate(template, mockSchema);
expect(result.domains).toHaveLength(1);
const domain = result.domains[0];
expect(domain).toBeDefined();
if (!domain || !domain.host) return;
expect(domain.host).toBeDefined();
expect(domain.host).toContain(mockSchema.projectName);
});
});
describe("environment variables processing", () => {
it("should process env vars with variable references", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {
main_domain: "${domain}",
secret_base: "${base64:64}",
},
config: {
domains: [],
env: {
BASE_URL: "http://${main_domain}",
SECRET_KEY_BASE: "${secret_base}",
},
},
};
const result = processTemplate(template, mockSchema);
expect(result.envs).toHaveLength(2);
const baseUrl = result.envs.find((env: string) =>
env.startsWith("BASE_URL="),
);
const secretKey = result.envs.find((env: string) =>
env.startsWith("SECRET_KEY_BASE="),
);
expect(baseUrl).toBeDefined();
expect(secretKey).toBeDefined();
if (!baseUrl || !secretKey) return;
expect(baseUrl).toContain(mockSchema.projectName);
const base64Value = secretKey.split("=")[1];
expect(base64Value).toBeDefined();
if (!base64Value) return;
expect(base64Value).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
expect(base64Value.length).toBeGreaterThanOrEqual(86);
expect(base64Value.length).toBeLessThanOrEqual(88);
});
it("should process env vars when provided as an array", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {},
config: {
domains: [],
env: [
'CLOUDFLARE_TUNNEL_TOKEN="<INSERT TOKEN>"',
'ANOTHER_VAR="some value"',
"DOMAIN=${domain}",
],
mounts: [],
},
};
const result = processTemplate(template, mockSchema);
expect(result.envs).toHaveLength(3);
// Should preserve exact format for static values
expect(result.envs[0]).toBe('CLOUDFLARE_TUNNEL_TOKEN="<INSERT TOKEN>"');
expect(result.envs[1]).toBe('ANOTHER_VAR="some value"');
// Should process variables in array items
expect(result.envs[2]).toContain(mockSchema.projectName);
});
it("should allow using utility functions directly in env vars", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {},
config: {
domains: [],
env: {
RANDOM_DOMAIN: "${domain}",
SECRET_KEY: "${base64:32}",
},
},
};
const result = processTemplate(template, mockSchema);
expect(result.envs).toHaveLength(2);
const randomDomainEnv = result.envs.find((env: string) =>
env.startsWith("RANDOM_DOMAIN="),
);
const secretKeyEnv = result.envs.find((env: string) =>
env.startsWith("SECRET_KEY="),
);
expect(randomDomainEnv).toBeDefined();
expect(secretKeyEnv).toBeDefined();
if (!randomDomainEnv || !secretKeyEnv) return;
expect(randomDomainEnv).toContain(mockSchema.projectName);
const base64Value = secretKeyEnv.split("=")[1];
expect(base64Value).toBeDefined();
if (!base64Value) return;
expect(base64Value).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
expect(base64Value.length).toBeGreaterThanOrEqual(42);
expect(base64Value.length).toBeLessThanOrEqual(44);
});
});
describe("mounts processing", () => {
it("should process mounts with variable references", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {
config_path: "/etc/config",
secret_key: "${base64:32}",
},
config: {
domains: [],
env: {},
mounts: [
{
filePath: "${config_path}/config.xml",
content: "secret_key=${secret_key}",
},
],
},
};
const result = processTemplate(template, mockSchema);
expect(result.mounts).toHaveLength(1);
const mount = result.mounts[0];
expect(mount).toBeDefined();
if (!mount) return;
expect(mount.filePath).toContain("/etc/config");
expect(mount.content).toMatch(/secret_key=[A-Za-z0-9+/]{32}/);
});
it("should allow using utility functions directly in mount content", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {},
config: {
domains: [],
env: {},
mounts: [
{
filePath: "/config/secrets.txt",
content: "random_domain=${domain}\nsecret=${base64:32}",
},
],
},
};
const result = processTemplate(template, mockSchema);
expect(result.mounts).toHaveLength(1);
const mount = result.mounts[0];
expect(mount).toBeDefined();
if (!mount) return;
expect(mount.content).toContain(mockSchema.projectName);
expect(mount.content).toMatch(/secret=[A-Za-z0-9+/]{32}/);
});
});
describe("complex template processing", () => {
it("should process a complete template with all features", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {
main_domain: "${domain}",
secret_base: "${base64:64}",
totp_key: "${base64:32}",
},
config: {
domains: [
{
serviceName: "plausible",
port: 8000,
host: "${main_domain}",
},
{
serviceName: "api",
port: 3000,
host: "api.${main_domain}",
},
],
env: {
BASE_URL: "http://${main_domain}",
SECRET_KEY_BASE: "${secret_base}",
TOTP_VAULT_KEY: "${totp_key}",
},
mounts: [
{
filePath: "/config/app.conf",
content: `
domain=\${main_domain}
secret=\${secret_base}
totp=\${totp_key}
`,
},
],
},
};
const result = processTemplate(template, mockSchema);
// Check domains
expect(result.domains).toHaveLength(2);
const [domain1, domain2] = result.domains;
expect(domain1).toBeDefined();
expect(domain2).toBeDefined();
if (!domain1 || !domain2) return;
expect(domain1.host).toBeDefined();
expect(domain1.host).toContain(mockSchema.projectName);
expect(domain2.host).toContain("api.");
expect(domain2.host).toContain(mockSchema.projectName);
// Check env vars
expect(result.envs).toHaveLength(3);
const baseUrl = result.envs.find((env: string) =>
env.startsWith("BASE_URL="),
);
const secretKey = result.envs.find((env: string) =>
env.startsWith("SECRET_KEY_BASE="),
);
const totpKey = result.envs.find((env: string) =>
env.startsWith("TOTP_VAULT_KEY="),
);
expect(baseUrl).toBeDefined();
expect(secretKey).toBeDefined();
expect(totpKey).toBeDefined();
if (!baseUrl || !secretKey || !totpKey) return;
expect(baseUrl).toContain(mockSchema.projectName);
// Check base64 lengths and format
const secretKeyValue = secretKey.split("=")[1];
const totpKeyValue = totpKey.split("=")[1];
expect(secretKeyValue).toBeDefined();
expect(totpKeyValue).toBeDefined();
if (!secretKeyValue || !totpKeyValue) return;
expect(secretKeyValue).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
expect(secretKeyValue.length).toBeGreaterThanOrEqual(86);
expect(secretKeyValue.length).toBeLessThanOrEqual(88);
expect(totpKeyValue).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
expect(totpKeyValue.length).toBeGreaterThanOrEqual(42);
expect(totpKeyValue.length).toBeLessThanOrEqual(44);
// Check mounts
expect(result.mounts).toHaveLength(1);
const mount = result.mounts[0];
expect(mount).toBeDefined();
if (!mount) return;
expect(mount.content).toContain(mockSchema.projectName);
expect(mount.content).toMatch(/secret=[A-Za-z0-9+/]{86,88}/);
expect(mount.content).toMatch(/totp=[A-Za-z0-9+/]{42,44}/);
});
});
describe("Should populate envs, domains and mounts in the case we didn't used any variable", () => {
it("should populate envs, domains and mounts in the case we didn't used any variable", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {},
config: {
domains: [
{
serviceName: "plausible",
port: 8000,
host: "${hash}",
},
],
env: {
BASE_URL: "http://${domain}",
SECRET_KEY_BASE: "${password:32}",
TOTP_VAULT_KEY: "${base64:128}",
},
mounts: [
{
filePath: "/config/secrets.txt",
content: "random_domain=${domain}\nsecret=${password:32}",
},
],
},
};
const result = processTemplate(template, mockSchema);
expect(result.envs).toHaveLength(3);
expect(result.domains).toHaveLength(1);
expect(result.mounts).toHaveLength(1);
});
});
});

View File

@@ -5,7 +5,7 @@ vi.mock("node:fs", () => ({
default: fs,
}));
import type { Admin, FileConfig } from "@dokploy/server";
import type { FileConfig, User } from "@dokploy/server";
import {
createDefaultServerTraefikConfig,
loadOrCreateConfig,
@@ -13,20 +13,57 @@ import {
} from "@dokploy/server";
import { beforeEach, expect, test, vi } from "vitest";
const baseAdmin: Admin = {
createdAt: "",
authId: "",
adminId: "string",
const baseAdmin: User = {
enablePaidFeatures: false,
metricsConfig: {
containers: {
refreshRate: 20,
services: {
include: [],
exclude: [],
},
},
server: {
type: "Dokploy",
cronJob: "",
port: 4500,
refreshRate: 20,
retentionDays: 2,
token: "",
thresholds: {
cpu: 0,
memory: 0,
},
urlCallback: "",
},
},
cleanupCacheApplications: false,
cleanupCacheOnCompose: false,
cleanupCacheOnPreviews: false,
createdAt: new Date(),
serverIp: null,
certificateType: "none",
host: null,
letsEncryptEmail: null,
sshPrivateKey: null,
enableDockerCleanup: false,
enableLogRotation: false,
logCleanupCron: null,
serversQuantity: 0,
stripeCustomerId: "",
stripeSubscriptionId: "",
banExpires: new Date(),
banned: true,
banReason: "",
email: "",
expirationDate: "",
id: "",
isRegistered: false,
name: "",
createdAt2: new Date().toISOString(),
emailVerified: false,
image: "",
updatedAt: new Date(),
twoFactorEnabled: false,
};
beforeEach(() => {
@@ -77,8 +114,6 @@ test("Should not touch config without host", () => {
});
test("Should remove websecure if https rollback to http", () => {
const originalConfig: FileConfig = loadOrCreateConfig("dokploy");
updateServerTraefik(
{ ...baseAdmin, certificateType: "letsencrypt" },
"example.com",

View File

@@ -14,6 +14,7 @@ const baseApp: ApplicationNested = {
branch: null,
dockerBuildStage: "",
registryUrl: "",
watchPaths: [],
buildArgs: null,
isPreviewDeploymentsActive: false,
previewBuildArgs: null,
@@ -23,10 +24,11 @@ const baseApp: ApplicationNested = {
previewPath: "/",
previewPort: 3000,
previewLimit: 0,
previewCustomCertResolver: null,
previewWildcard: "",
project: {
env: "",
adminId: "",
organizationId: "",
name: "",
description: "",
createdAt: "",
@@ -103,6 +105,7 @@ const baseDomain: Domain = {
port: null,
serviceName: "",
composeId: "",
customCertResolver: null,
domainType: "application",
uniqueConfigKey: 1,
previewDeploymentId: "",

View File

@@ -13,6 +13,7 @@ export default defineConfig({
NODE: "test",
},
},
plugins: [tsconfigPaths()],
resolve: {
alias: {
"@dokploy/server": path.resolve(

View File

@@ -1,124 +0,0 @@
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { CardTitle } from "@/components/ui/card";
import {
InputOTP,
InputOTPGroup,
InputOTPSlot,
} from "@/components/ui/input-otp";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const Login2FASchema = z.object({
pin: z.string().min(6, {
message: "Pin is required",
}),
});
type Login2FA = z.infer<typeof Login2FASchema>;
interface Props {
authId: string;
}
export const Login2FA = ({ authId }: Props) => {
const { push } = useRouter();
const { mutateAsync, isLoading, isError, error } =
api.auth.verifyLogin2FA.useMutation();
const form = useForm<Login2FA>({
defaultValues: {
pin: "",
},
resolver: zodResolver(Login2FASchema),
});
useEffect(() => {
form.reset({
pin: "",
});
}, [form, form.reset, form.formState.isSubmitSuccessful]);
const onSubmit = async (data: Login2FA) => {
await mutateAsync({
pin: data.pin,
id: authId,
})
.then(() => {
toast.success("Signin successfully", {
duration: 2000,
});
push("/dashboard/projects");
})
.catch(() => {
toast.error("Signin failed", {
duration: 2000,
});
});
};
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
{isError && (
<div className="flex flex-row gap-4 rounded-lg items-center bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
<CardTitle className="text-xl font-bold">2FA Login</CardTitle>
<FormField
control={form.control}
name="pin"
render={({ field }) => (
<FormItem className="flex flex-col justify-center max-sm:items-center">
<FormLabel>Pin</FormLabel>
<FormControl>
<InputOTP maxLength={6} {...field}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</FormControl>
<FormDescription>
Please enter the 6 digits code provided by your authenticator
app.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button isLoading={isLoading} type="submit">
Submit 2FA
</Button>
</form>
</Form>
);
};

View File

@@ -130,7 +130,7 @@ const createStringToJSONSchema = (schema: z.ZodTypeAny) => {
}
try {
return JSON.parse(str);
} catch (e) {
} catch (_e) {
ctx.addIssue({ code: "custom", message: "Invalid JSON format" });
return z.NEVER;
}
@@ -259,7 +259,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
refetch();
})
.catch(() => {
toast.error("Error to update the swarm settings");
toast.error("Error updating the swarm settings");
});
};
return (

View File

@@ -29,7 +29,6 @@ import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Server } from "lucide-react";
import Link from "next/link";
import React from "react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -94,7 +93,7 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
});
})
.catch(() => {
toast.error("Error to update the command");
toast.error("Error updating the command");
});
};

View File

@@ -17,7 +17,6 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import React from "react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -71,7 +70,7 @@ export const AddCommand = ({ applicationId }: Props) => {
});
})
.catch(() => {
toast.error("Error to update the command");
toast.error("Error updating the command");
});
};
@@ -81,7 +80,8 @@ export const AddCommand = ({ applicationId }: Props) => {
<div>
<CardTitle className="text-xl">Run Command</CardTitle>
<CardDescription>
Run a custom command in the container
Run a custom command in the container after the application
initialized
</CardDescription>
</div>
</CardHeader>

View File

@@ -0,0 +1,347 @@
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Code2, Globe2, HardDrive } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
const ImportSchema = z.object({
base64: z.string(),
});
type ImportType = z.infer<typeof ImportSchema>;
interface Props {
composeId: string;
}
export const ShowImport = ({ composeId }: Props) => {
const [showModal, setShowModal] = useState(false);
const [showMountContent, setShowMountContent] = useState(false);
const [selectedMount, setSelectedMount] = useState<{
filePath: string;
content: string;
} | null>(null);
const [templateInfo, setTemplateInfo] = useState<{
compose: string;
template: {
domains: Array<{
serviceName: string;
port: number;
path?: string;
host?: string;
}>;
envs: string[];
mounts: Array<{
filePath: string;
content: string;
}>;
};
} | null>(null);
const utils = api.useUtils();
const { mutateAsync: processTemplate, isLoading: isLoadingTemplate } =
api.compose.processTemplate.useMutation();
const {
mutateAsync: importTemplate,
isLoading: isImporting,
isSuccess: isImportSuccess,
} = api.compose.import.useMutation();
const form = useForm<ImportType>({
defaultValues: {
base64: "",
},
resolver: zodResolver(ImportSchema),
});
useEffect(() => {
form.reset({
base64: "",
});
}, [isImportSuccess]);
const onSubmit = async () => {
const base64 = form.getValues("base64");
if (!base64) {
toast.error("Please enter a base64 template");
return;
}
try {
await importTemplate({
composeId,
base64,
});
toast.success("Template imported successfully");
await utils.compose.one.invalidate({
composeId,
});
setShowModal(false);
} catch (_error) {
toast.error("Error importing template");
}
};
const handleLoadTemplate = async () => {
const base64 = form.getValues("base64");
if (!base64) {
toast.error("Please enter a base64 template");
return;
}
try {
const result = await processTemplate({
composeId,
base64,
});
setTemplateInfo(result);
setShowModal(true);
} catch (_error) {
toast.error("Error processing template");
}
};
const handleShowMountContent = (mount: {
filePath: string;
content: string;
}) => {
setSelectedMount(mount);
setShowMountContent(true);
};
return (
<>
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">Import</CardTitle>
<CardDescription>Import your Template configuration</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<AlertBlock type="warning">
Warning: Importing a template will remove all existing environment
variables, mounts, and domains from this service.
</AlertBlock>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="base64"
render={({ field }) => (
<FormItem>
<FormLabel>Configuration (Base64)</FormLabel>
<FormControl>
<Textarea
placeholder="Enter your Base64 configuration here..."
className="font-mono min-h-[200px]"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-2">
<Button
type="button"
className="w-fit"
variant="outline"
isLoading={isLoadingTemplate}
onClick={handleLoadTemplate}
>
Load
</Button>
</div>
<Dialog open={showModal} onOpenChange={setShowModal}>
<DialogContent className="max-h-[80vh] max-w-[50vw] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-2xl font-bold">
Template Information
</DialogTitle>
<DialogDescription className="space-y-2">
<p>Review the template information before importing</p>
<AlertBlock type="warning">
Warning: This will remove all existing environment
variables, mounts, and domains from this service.
</AlertBlock>
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-6">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Code2 className="h-5 w-5 text-primary" />
<h3 className="text-lg font-semibold">
Docker Compose
</h3>
</div>
<CodeEditor
language="yaml"
value={templateInfo?.compose || ""}
className="font-mono"
readOnly
/>
</div>
<Separator />
{templateInfo?.template.domains &&
templateInfo.template.domains.length > 0 && (
<div className="space-y-4">
<div className="flex items-center gap-2">
<Globe2 className="h-5 w-5 text-primary" />
<h3 className="text-lg font-semibold">Domains</h3>
</div>
<div className="grid grid-cols-1 gap-3">
{templateInfo.template.domains.map(
(domain, index) => (
<div
key={index}
className="rounded-lg border bg-card p-3 text-card-foreground shadow-sm"
>
<div className="font-medium">
{domain.serviceName}
</div>
<div className="text-sm text-muted-foreground space-y-1">
<div>Port: {domain.port}</div>
{domain.host && (
<div>Host: {domain.host}</div>
)}
{domain.path && (
<div>Path: {domain.path}</div>
)}
</div>
</div>
),
)}
</div>
</div>
)}
{templateInfo?.template.envs &&
templateInfo.template.envs.length > 0 && (
<div className="space-y-4">
<div className="flex items-center gap-2">
<Code2 className="h-5 w-5 text-primary" />
<h3 className="text-lg font-semibold">
Environment Variables
</h3>
</div>
<div className="grid grid-cols-1 gap-2">
{templateInfo.template.envs.map((env, index) => (
<div
key={index}
className="rounded-lg border bg-card p-2 font-mono text-sm"
>
{env}
</div>
))}
</div>
</div>
)}
{templateInfo?.template.mounts &&
templateInfo.template.mounts.length > 0 && (
<div className="space-y-4">
<div className="flex items-center gap-2">
<HardDrive className="h-5 w-5 text-primary" />
<h3 className="text-lg font-semibold">Mounts</h3>
</div>
<div className="grid grid-cols-1 gap-2">
{templateInfo.template.mounts.map(
(mount, index) => (
<div
key={index}
className="rounded-lg border bg-card p-2 font-mono text-sm hover:bg-accent cursor-pointer transition-colors"
onClick={() => handleShowMountContent(mount)}
>
{mount.filePath}
</div>
),
)}
</div>
</div>
)}
</div>
<div className="flex justify-end gap-2 pt-4">
<Button
variant="outline"
onClick={() => setShowModal(false)}
>
Cancel
</Button>
<Button
isLoading={isImporting}
type="submit"
onClick={form.handleSubmit(onSubmit)}
className="w-fit"
>
Import
</Button>
</div>
</DialogContent>
</Dialog>
</form>
</Form>
</CardContent>
</Card>
<Dialog open={showMountContent} onOpenChange={setShowMountContent}>
<DialogContent className="max-w-[50vw]">
<DialogHeader>
<DialogTitle className="text-xl font-bold">
{selectedMount?.filePath}
</DialogTitle>
<DialogDescription>Mount File Content</DialogDescription>
</DialogHeader>
<ScrollArea className="h-[25vh] pr-4">
<CodeEditor
language="yaml"
value={selectedMount?.content || ""}
className="font-mono"
readOnly
/>
</ScrollArea>
<div className="flex justify-end gap-2 pt-4">
<Button onClick={() => setShowMountContent(false)}>Close</Button>
</div>
</DialogContent>
</Dialog>
</>
);
};

View File

@@ -1,63 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { TrashIcon } from "lucide-react";
import { toast } from "sonner";
interface Props {
portId: string;
}
export const DeletePort = ({ portId }: Props) => {
const utils = api.useUtils();
const { mutateAsync, isLoading } = api.port.delete.useMutation();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the port
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
portId,
})
.then((data) => {
utils.application.one.invalidate({
applicationId: data?.applicationId,
});
toast.success("Port delete succesfully");
})
.catch(() => {
toast.error("Error to delete the port");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -27,7 +27,7 @@ import {
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PlusIcon } from "lucide-react";
import { PenBoxIcon, PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -45,18 +45,29 @@ type AddPort = z.infer<typeof AddPortSchema>;
interface Props {
applicationId: string;
portId?: string;
children?: React.ReactNode;
}
export const AddPort = ({
export const HandlePorts = ({
applicationId,
portId,
children = <PlusIcon className="h-4 w-4" />,
}: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { mutateAsync, isLoading, error, isError } =
api.port.create.useMutation();
const { data } = api.port.one.useQuery(
{
portId: portId ?? "",
},
{
enabled: !!portId,
},
);
const { mutateAsync, isLoading, error, isError } = portId
? api.port.update.useMutation()
: api.port.create.useMutation();
const form = useForm<AddPort>({
defaultValues: {
@@ -68,32 +79,46 @@ export const AddPort = ({
useEffect(() => {
form.reset({
publishedPort: 0,
targetPort: 0,
publishedPort: data?.publishedPort ?? 0,
targetPort: data?.targetPort ?? 0,
protocol: data?.protocol ?? "tcp",
});
}, [form, form.reset, form.formState.isSubmitSuccessful]);
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
const onSubmit = async (data: AddPort) => {
await mutateAsync({
applicationId,
...data,
portId: portId || "",
})
.then(async () => {
toast.success("Port Created");
toast.success(portId ? "Port Updated" : "Port Created");
await utils.application.one.invalidate({
applicationId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the port");
toast.error(
portId ? "Error updating the port" : "Error creating the port",
);
});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button>{children}</Button>
{portId ? (
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 "
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
) : (
<Button>{children}</Button>
)}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
@@ -204,7 +229,7 @@ export const AddPort = ({
form="hook-form-add-port"
type="submit"
>
Create
{portId ? "Update" : "Create"}
</Button>
</DialogFooter>
</Form>

View File

@@ -1,4 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@@ -7,23 +9,24 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { Rss } from "lucide-react";
import React from "react";
import { AddPort } from "./add-port";
import { DeletePort } from "./delete-port";
import { UpdatePort } from "./update-port";
import { Rss, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { HandlePorts } from "./handle-ports";
interface Props {
applicationId: string;
}
export const ShowPorts = ({ applicationId }: Props) => {
const { data } = api.application.one.useQuery(
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
},
{ enabled: !!applicationId },
);
const { mutateAsync: deletePort, isLoading: isRemoving } =
api.port.delete.useMutation();
return (
<Card className="bg-background">
<CardHeader className="flex flex-row justify-between flex-wrap gap-4">
@@ -35,7 +38,7 @@ export const ShowPorts = ({ applicationId }: Props) => {
</div>
{data && data?.ports.length > 0 && (
<AddPort applicationId={applicationId}>Add Port</AddPort>
<HandlePorts applicationId={applicationId}>Add Port</HandlePorts>
)}
</CardHeader>
<CardContent className="flex flex-col gap-4">
@@ -45,7 +48,7 @@ export const ShowPorts = ({ applicationId }: Props) => {
<span className="text-base text-muted-foreground">
No ports configured
</span>
<AddPort applicationId={applicationId}>Add Port</AddPort>
<HandlePorts applicationId={applicationId}>Add Port</HandlePorts>
</div>
) : (
<div className="flex flex-col pt-2 gap-4">
@@ -78,8 +81,36 @@ export const ShowPorts = ({ applicationId }: Props) => {
</div>
</div>
<div className="flex flex-row gap-4">
<UpdatePort portId={port.portId} />
<DeletePort portId={port.portId} />
<HandlePorts
applicationId={applicationId}
portId={port.portId}
/>
<DialogAction
title="Delete Port"
description="Are you sure you want to delete this port?"
type="destructive"
onClick={async () => {
await deletePort({
portId: port.portId,
})
.then(() => {
refetch();
toast.success("Port deleted successfully");
})
.catch(() => {
toast.error("Error deleting port");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10 "
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</div>
</div>
</div>

View File

@@ -1,195 +0,0 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input, NumberInput } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon, Pencil } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const UpdatePortSchema = z.object({
publishedPort: z.number().int().min(1).max(65535),
targetPort: z.number().int().min(1).max(65535),
protocol: z.enum(["tcp", "udp"], {
required_error: "Protocol is required",
invalid_type_error: "Protocol must be a valid protocol",
}),
});
type UpdatePort = z.infer<typeof UpdatePortSchema>;
interface Props {
portId: string;
}
export const UpdatePort = ({ portId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { data } = api.port.one.useQuery(
{
portId,
},
{
enabled: !!portId,
},
);
const { mutateAsync, isLoading, error, isError } =
api.port.update.useMutation();
const form = useForm<UpdatePort>({
defaultValues: {},
resolver: zodResolver(UpdatePortSchema),
});
useEffect(() => {
if (data) {
form.reset({
publishedPort: data.publishedPort,
targetPort: data.targetPort,
protocol: data.protocol,
});
}
}, [form, form.reset, data]);
const onSubmit = async (data: UpdatePort) => {
await mutateAsync({
portId,
publishedPort: data.publishedPort,
targetPort: data.targetPort,
protocol: data.protocol,
})
.then(async (response) => {
toast.success("Port Updated");
await utils.application.one.invalidate({
applicationId: response?.applicationId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the port");
});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<PenBoxIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Update</DialogTitle>
<DialogDescription>Update the port</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
id="hook-form-update-redirect"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="publishedPort"
render={({ field }) => (
<FormItem>
<FormLabel>Published Port</FormLabel>
<FormControl>
<NumberInput placeholder="1-65535" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="targetPort"
render={({ field }) => (
<FormItem>
<FormLabel>Target Port</FormLabel>
<FormControl>
<NumberInput placeholder="1-65535" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="protocol"
render={({ field }) => {
return (
<FormItem className="md:col-span-2">
<FormLabel>Protocol</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a protocol" />
</SelectTrigger>
</FormControl>
<SelectContent defaultValue={"none"}>
<SelectItem value={"none"} disabled>
None
</SelectItem>
<SelectItem value={"tcp"}>TCP</SelectItem>
<SelectItem value={"udp"}>UDP</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
);
}}
/>
</div>
</form>
<DialogFooter>
<Button
isLoading={isLoading}
form="hook-form-update-redirect"
type="submit"
>
Update
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,66 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { TrashIcon } from "lucide-react";
import { toast } from "sonner";
interface Props {
redirectId: string;
}
export const DeleteRedirect = ({ redirectId }: Props) => {
const utils = api.useUtils();
const { mutateAsync, isLoading } = api.redirects.delete.useMutation();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
redirect
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
redirectId,
})
.then((data) => {
utils.application.one.invalidate({
applicationId: data?.applicationId,
});
utils.application.readTraefikConfig.invalidate({
applicationId: data?.applicationId,
});
toast.success("Redirect delete succesfully");
})
.catch(() => {
toast.error("Error to delete the redirect");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -31,7 +31,7 @@ import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PlusIcon } from "lucide-react";
import { PenBoxIcon, PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -77,19 +77,32 @@ const redirectPresets = [
interface Props {
applicationId: string;
redirectId?: string;
children?: React.ReactNode;
}
export const AddRedirect = ({
export const HandleRedirect = ({
applicationId,
redirectId,
children = <PlusIcon className="w-4 h-4" />,
}: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [presetSelected, setPresetSelected] = useState("");
const { data, refetch } = api.redirects.one.useQuery(
{
redirectId: redirectId || "",
},
{
enabled: !!redirectId,
},
);
const utils = api.useUtils();
const { mutateAsync, isLoading, error, isError } =
api.redirects.create.useMutation();
const { mutateAsync, isLoading, error, isError } = redirectId
? api.redirects.update.useMutation()
: api.redirects.create.useMutation();
const form = useForm<AddRedirect>({
defaultValues: {
@@ -102,29 +115,35 @@ export const AddRedirect = ({
useEffect(() => {
form.reset({
permanent: false,
regex: "",
replacement: "",
permanent: data?.permanent || false,
regex: data?.regex || "",
replacement: data?.replacement || "",
});
}, [form, form.reset, form.formState.isSubmitSuccessful]);
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
const onSubmit = async (data: AddRedirect) => {
await mutateAsync({
applicationId,
...data,
redirectId: redirectId || "",
})
.then(async () => {
toast.success("Redirect Created");
toast.success(redirectId ? "Redirect Updated" : "Redirect Created");
await utils.application.one.invalidate({
applicationId,
});
refetch();
await utils.application.readTraefikConfig.invalidate({
applicationId,
});
onDialogToggle(false);
})
.catch(() => {
toast.error("Error to create the redirect");
toast.error(
redirectId
? "Error updating the redirect"
: "Error creating the redirect",
);
});
};
@@ -148,7 +167,17 @@ export const AddRedirect = ({
return (
<Dialog open={isOpen} onOpenChange={onDialogToggle}>
<DialogTrigger asChild>
<Button>{children}</Button>
{redirectId ? (
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 "
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
) : (
<Button>{children}</Button>
)}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
@@ -243,7 +272,7 @@ export const AddRedirect = ({
form="hook-form-add-redirect"
type="submit"
>
Create
{redirectId ? "Update" : "Create"}
</Button>
</DialogFooter>
</Form>

View File

@@ -1,3 +1,5 @@
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@@ -6,23 +8,27 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { Split } from "lucide-react";
import React from "react";
import { AddRedirect } from "./add-redirect";
import { DeleteRedirect } from "./delete-redirect";
import { UpdateRedirect } from "./update-redirect";
import { Split, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { HandleRedirect } from "./handle-redirect";
interface Props {
applicationId: string;
}
export const ShowRedirects = ({ applicationId }: Props) => {
const { data } = api.application.one.useQuery(
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
},
{ enabled: !!applicationId },
);
const { mutateAsync: deleteRedirect, isLoading: isRemoving } =
api.redirects.delete.useMutation();
const utils = api.useUtils();
return (
<Card className="bg-background">
<CardHeader className="flex flex-row justify-between flex-wrap gap-4">
@@ -35,7 +41,9 @@ export const ShowRedirects = ({ applicationId }: Props) => {
</div>
{data && data?.redirects.length > 0 && (
<AddRedirect applicationId={applicationId}>Add Redirect</AddRedirect>
<HandleRedirect applicationId={applicationId}>
Add Redirect
</HandleRedirect>
)}
</CardHeader>
<CardContent className="flex flex-col gap-4">
@@ -45,9 +53,9 @@ export const ShowRedirects = ({ applicationId }: Props) => {
<span className="text-base text-muted-foreground">
No redirects configured
</span>
<AddRedirect applicationId={applicationId}>
<HandleRedirect applicationId={applicationId}>
Add Redirect
</AddRedirect>
</HandleRedirect>
</div>
) : (
<div className="flex flex-col pt-2">
@@ -76,8 +84,40 @@ export const ShowRedirects = ({ applicationId }: Props) => {
</div>
</div>
<div className="flex flex-row gap-4">
<UpdateRedirect redirectId={redirect.redirectId} />
<DeleteRedirect redirectId={redirect.redirectId} />
<HandleRedirect
redirectId={redirect.redirectId}
applicationId={applicationId}
/>
<DialogAction
title="Delete Redirect"
description="Are you sure you want to delete this redirect?"
type="destructive"
onClick={async () => {
await deleteRedirect({
redirectId: redirect.redirectId,
})
.then(() => {
refetch();
utils.application.readTraefikConfig.invalidate({
applicationId,
});
toast.success("Redirect deleted successfully");
})
.catch(() => {
toast.error("Error deleting redirect");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10"
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</div>
</div>
</div>

View File

@@ -1,182 +0,0 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon, Pencil } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const UpdateRedirectSchema = z.object({
regex: z.string().min(1, "Regex required"),
permanent: z.boolean().default(false),
replacement: z.string().min(1, "Replacement required"),
});
type UpdateRedirect = z.infer<typeof UpdateRedirectSchema>;
interface Props {
redirectId: string;
}
export const UpdateRedirect = ({ redirectId }: Props) => {
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const { data } = api.redirects.one.useQuery(
{
redirectId,
},
{
enabled: !!redirectId,
},
);
const { mutateAsync, isLoading, error, isError } =
api.redirects.update.useMutation();
const form = useForm<UpdateRedirect>({
defaultValues: {
permanent: false,
regex: "",
replacement: "",
},
resolver: zodResolver(UpdateRedirectSchema),
});
useEffect(() => {
if (data) {
form.reset({
permanent: data.permanent || false,
regex: data.regex || "",
replacement: data.replacement || "",
});
}
}, [form, form.reset, data]);
const onSubmit = async (data: UpdateRedirect) => {
await mutateAsync({
redirectId,
permanent: data.permanent,
regex: data.regex,
replacement: data.replacement,
})
.then(async (response) => {
toast.success("Redirect Updated");
await utils.application.one.invalidate({
applicationId: response?.applicationId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the redirect");
});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<PenBoxIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Update</DialogTitle>
<DialogDescription>Update the redirect</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
id="hook-form-update-redirect"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="regex"
render={({ field }) => (
<FormItem>
<FormLabel>Regex</FormLabel>
<FormControl>
<Input placeholder="^http://localhost/(.*)" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="replacement"
render={({ field }) => (
<FormItem>
<FormLabel>Replacement</FormLabel>
<FormControl>
<Input placeholder="http://mydomain/$${1}" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="permanent"
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>Permanent</FormLabel>
<FormDescription>
Set the permanent option to true to apply a permanent
redirection.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
</form>
<DialogFooter>
<Button
isLoading={isLoading}
form="hook-form-update-redirect"
type="submit"
>
Update
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,66 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { TrashIcon } from "lucide-react";
import { toast } from "sonner";
interface Props {
securityId: string;
}
export const DeleteSecurity = ({ securityId }: Props) => {
const utils = api.useUtils();
const { mutateAsync, isLoading } = api.security.delete.useMutation();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
security
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
securityId,
})
.then((data) => {
utils.application.one.invalidate({
applicationId: data?.applicationId,
});
utils.application.readTraefikConfig.invalidate({
applicationId: data?.applicationId,
});
toast.success("Security delete succesfully");
})
.catch(() => {
toast.error("Error to delete the security");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -20,7 +20,7 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PlusIcon } from "lucide-react";
import { PenBoxIcon, PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -35,17 +35,29 @@ type AddSecurity = z.infer<typeof AddSecuritychema>;
interface Props {
applicationId: string;
securityId?: string;
children?: React.ReactNode;
}
export const AddSecurity = ({
export const HandleSecurity = ({
applicationId,
securityId,
children = <PlusIcon className="h-4 w-4" />,
}: Props) => {
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading, error, isError } =
api.security.create.useMutation();
const { data } = api.security.one.useQuery(
{
securityId: securityId ?? "",
},
{
enabled: !!securityId,
},
);
const { mutateAsync, isLoading, error, isError } = securityId
? api.security.update.useMutation()
: api.security.create.useMutation();
const form = useForm<AddSecurity>({
defaultValues: {
@@ -56,16 +68,20 @@ export const AddSecurity = ({
});
useEffect(() => {
form.reset();
}, [form, form.reset, form.formState.isSubmitSuccessful]);
form.reset({
username: data?.username || "",
password: data?.password || "",
});
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
const onSubmit = async (data: AddSecurity) => {
await mutateAsync({
applicationId,
...data,
securityId: securityId || "",
})
.then(async () => {
toast.success("Security Created");
toast.success(securityId ? "Security Updated" : "Security Created");
await utils.application.one.invalidate({
applicationId,
});
@@ -75,20 +91,34 @@ export const AddSecurity = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the security");
toast.error(
securityId
? "Error updating the security"
: "Error creating security",
);
});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button>{children}</Button>
{securityId ? (
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 "
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
) : (
<Button>{children}</Button>
)}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Security</DialogTitle>
<DialogDescription>
Add security to your application
{securityId ? "Update" : "Add"} security to your application
</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
@@ -137,7 +167,7 @@ export const AddSecurity = ({
form="hook-form-add-security"
type="submit"
>
Create
{securityId ? "Update" : "Create"}
</Button>
</DialogFooter>
</Form>

View File

@@ -1,3 +1,5 @@
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@@ -6,23 +8,26 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { LockKeyhole } from "lucide-react";
import React from "react";
import { AddSecurity } from "./add-security";
import { DeleteSecurity } from "./delete-security";
import { UpdateSecurity } from "./update-security";
import { LockKeyhole, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { HandleSecurity } from "./handle-security";
interface Props {
applicationId: string;
}
export const ShowSecurity = ({ applicationId }: Props) => {
const { data } = api.application.one.useQuery(
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
},
{ enabled: !!applicationId },
);
const { mutateAsync: deleteSecurity, isLoading: isRemoving } =
api.security.delete.useMutation();
const utils = api.useUtils();
return (
<Card className="bg-background">
<CardHeader className="flex flex-row justify-between flex-wrap gap-4">
@@ -32,7 +37,9 @@ export const ShowSecurity = ({ applicationId }: Props) => {
</div>
{data && data?.security.length > 0 && (
<AddSecurity applicationId={applicationId}>Add Security</AddSecurity>
<HandleSecurity applicationId={applicationId}>
Add Security
</HandleSecurity>
)}
</CardHeader>
<CardContent className="flex flex-col gap-4">
@@ -42,9 +49,9 @@ export const ShowSecurity = ({ applicationId }: Props) => {
<span className="text-base text-muted-foreground">
No security configured
</span>
<AddSecurity applicationId={applicationId}>
<HandleSecurity applicationId={applicationId}>
Add Security
</AddSecurity>
</HandleSecurity>
</div>
) : (
<div className="flex flex-col pt-2">
@@ -67,8 +74,39 @@ export const ShowSecurity = ({ applicationId }: Props) => {
</div>
</div>
<div className="flex flex-row gap-2">
<UpdateSecurity securityId={security.securityId} />
<DeleteSecurity securityId={security.securityId} />
<HandleSecurity
securityId={security.securityId}
applicationId={applicationId}
/>
<DialogAction
title="Delete Security"
description="Are you sure you want to delete this security?"
type="destructive"
onClick={async () => {
await deleteSecurity({
securityId: security.securityId,
})
.then(() => {
refetch();
utils.application.readTraefikConfig.invalidate({
applicationId,
});
toast.success("Security deleted successfully");
})
.catch(() => {
toast.error("Error deleting security");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10"
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</div>
</div>
</div>

View File

@@ -1,155 +0,0 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon, Pencil } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const UpdateSecuritySchema = z.object({
username: z.string().min(1, "Username is required"),
password: z.string().min(1, "Password is required"),
});
type UpdateSecurity = z.infer<typeof UpdateSecuritySchema>;
interface Props {
securityId: string;
}
export const UpdateSecurity = ({ securityId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { data } = api.security.one.useQuery(
{
securityId,
},
{
enabled: !!securityId,
},
);
const { mutateAsync, isLoading, error, isError } =
api.security.update.useMutation();
const form = useForm<UpdateSecurity>({
defaultValues: {
username: "",
password: "",
},
resolver: zodResolver(UpdateSecuritySchema),
});
useEffect(() => {
if (data) {
form.reset({
username: data.username || "",
password: data.password || "",
});
}
}, [form, form.reset, data]);
const onSubmit = async (data: UpdateSecurity) => {
await mutateAsync({
securityId,
username: data.username,
password: data.password,
})
.then(async (response) => {
toast.success("Security Updated");
await utils.application.one.invalidate({
applicationId: response?.applicationId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the security");
});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<PenBoxIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Update</DialogTitle>
<DialogDescription>Update the security</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
id="hook-form-update-security"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4 "
>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="test1" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input placeholder="test" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</form>
<DialogFooter>
<Button
isLoading={isLoading}
form="hook-form-update-security"
type="submit"
>
Update
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,231 +0,0 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const addResourcesApplication = z.object({
memoryReservation: z.number().nullable().optional(),
cpuLimit: z.number().nullable().optional(),
memoryLimit: z.number().nullable().optional(),
cpuReservation: z.number().nullable().optional(),
});
interface Props {
applicationId: string;
}
type AddResourcesApplication = z.infer<typeof addResourcesApplication>;
export const ShowApplicationResources = ({ applicationId }: Props) => {
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
},
{ enabled: !!applicationId },
);
const { mutateAsync, isLoading } = api.application.update.useMutation();
const form = useForm<AddResourcesApplication>({
defaultValues: {},
resolver: zodResolver(addResourcesApplication),
});
useEffect(() => {
if (data) {
form.reset({
cpuLimit: data?.cpuLimit || undefined,
cpuReservation: data?.cpuReservation || undefined,
memoryLimit: data?.memoryLimit || undefined,
memoryReservation: data?.memoryReservation || undefined,
});
}
}, [data, form, form.reset]);
const onSubmit = async (formData: AddResourcesApplication) => {
await mutateAsync({
applicationId,
cpuLimit: formData.cpuLimit || null,
cpuReservation: formData.cpuReservation || null,
memoryLimit: formData.memoryLimit || null,
memoryReservation: formData.memoryReservation || null,
})
.then(async () => {
toast.success("Resources Updated");
await refetch();
})
.catch(() => {
toast.error("Error to Update the resources");
});
};
return (
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">Resources</CardTitle>
<CardDescription>
If you want to decrease or increase the resources to a specific.
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<AlertBlock type="info">
Please remember to click Redeploy after modify the resources to apply
the changes.
</AlertBlock>
<Form {...form}>
<form
id="hook-form"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-8 "
>
<div className="grid w-full md:grid-cols-2 gap-4">
<FormField
control={form.control}
name="memoryReservation"
render={({ field }) => (
<FormItem>
<FormLabel>Memory Reservation</FormLabel>
<FormControl>
<Input
placeholder="256 MB"
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
field.onChange(number);
}
}
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="memoryLimit"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Memory Limit</FormLabel>
<FormControl>
<Input
placeholder={"1024 MB"}
{...field}
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
field.onChange(null);
} else {
const number = Number.parseInt(value, 10);
if (!Number.isNaN(number)) {
field.onChange(number);
}
}
}}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="cpuLimit"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Cpu Limit</FormLabel>
<FormControl>
<Input
placeholder={"2"}
{...field}
type="number"
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (
value === "" ||
/^[0-9]*\.?[0-9]*$/.test(value)
) {
const float = Number.parseFloat(value);
field.onChange(float);
}
}}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="cpuReservation"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Cpu Reservation</FormLabel>
<FormControl>
<Input
placeholder={"1"}
{...field}
type="number"
value={field.value?.toString() || ""}
onChange={(e) => {
const value = e.target.value;
if (
value === "" ||
/^[0-9]*\.?[0-9]*$/.test(value)
) {
const float = Number.parseFloat(value);
field.onChange(float);
}
}}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
<div className="flex w-full justify-end">
<Button isLoading={isLoading} type="submit">
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
);
};

View File

@@ -0,0 +1,287 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { InfoIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const addResourcesSchema = z.object({
memoryReservation: z.string().optional(),
cpuLimit: z.string().optional(),
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
});
export type ServiceType =
| "postgres"
| "mongo"
| "redis"
| "mysql"
| "mariadb"
| "application";
interface Props {
id: string;
type: ServiceType | "application";
}
type AddResources = z.infer<typeof addResourcesSchema>;
export const ShowResources = ({ id, type }: Props) => {
const queryMap = {
postgres: () =>
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
mariadb: () =>
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
application: () =>
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
};
const { data, refetch } = queryMap[type]
? queryMap[type]()
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
const mutationMap = {
postgres: () => api.postgres.update.useMutation(),
redis: () => api.redis.update.useMutation(),
mysql: () => api.mysql.update.useMutation(),
mariadb: () => api.mariadb.update.useMutation(),
application: () => api.application.update.useMutation(),
mongo: () => api.mongo.update.useMutation(),
};
const { mutateAsync, isLoading } = mutationMap[type]
? mutationMap[type]()
: api.mongo.update.useMutation();
const form = useForm<AddResources>({
defaultValues: {
cpuLimit: "",
cpuReservation: "",
memoryLimit: "",
memoryReservation: "",
},
resolver: zodResolver(addResourcesSchema),
});
useEffect(() => {
if (data) {
form.reset({
cpuLimit: data?.cpuLimit || undefined,
cpuReservation: data?.cpuReservation || undefined,
memoryLimit: data?.memoryLimit || undefined,
memoryReservation: data?.memoryReservation || undefined,
});
}
}, [data, form, form.reset]);
const onSubmit = async (formData: AddResources) => {
await mutateAsync({
mongoId: id || "",
postgresId: id || "",
redisId: id || "",
mysqlId: id || "",
mariadbId: id || "",
applicationId: id || "",
cpuLimit: formData.cpuLimit || null,
cpuReservation: formData.cpuReservation || null,
memoryLimit: formData.memoryLimit || null,
memoryReservation: formData.memoryReservation || null,
})
.then(async () => {
toast.success("Resources Updated");
await refetch();
})
.catch(() => {
toast.error("Error updating the resources");
});
};
return (
<Card className="bg-background">
<CardHeader>
<CardTitle className="text-xl">Resources</CardTitle>
<CardDescription>
If you want to decrease or increase the resources to a specific.
application or database
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<AlertBlock type="info">
Please remember to click Redeploy after modify the resources to apply
the changes.
</AlertBlock>
<Form {...form}>
<form
id="hook-form"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-8 "
>
<div className="grid w-full md:grid-cols-2 gap-4">
<FormField
control={form.control}
name="memoryLimit"
render={({ field }) => {
return (
<FormItem>
<div className="flex items-center gap-2">
<FormLabel>Memory Limit</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
Memory hard limit in bytes. Example: 1GB =
1073741824 bytes
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder="1073741824 (1GB in bytes)"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="memoryReservation"
render={({ field }) => (
<FormItem>
<div className="flex items-center gap-2">
<FormLabel>Memory Reservation</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
Memory soft limit in bytes. Example: 256MB =
268435456 bytes
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder="268435456 (256MB in bytes)"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="cpuLimit"
render={({ field }) => {
return (
<FormItem>
<div className="flex items-center gap-2">
<FormLabel>CPU Limit</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
CPU quota in units of 10^-9 CPUs. Example: 2
CPUs = 2000000000
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
placeholder="2000000000 (2 CPUs)"
{...field}
value={field.value?.toString() || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="cpuReservation"
render={({ field }) => {
return (
<FormItem>
<div className="flex items-center gap-2">
<FormLabel>CPU Reservation</FormLabel>
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger>
<InfoIcon className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>
CPU shares (relative weight). Example: 1 CPU =
1000000000
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input placeholder="1000000000 (1 CPU)" {...field} />
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</div>
<div className="flex w-full justify-end">
<Button isLoading={isLoading} type="submit">
Save
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
);
};

View File

@@ -8,7 +8,6 @@ import {
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { File, Loader2 } from "lucide-react";
import React from "react";
import { UpdateTraefikConfig } from "./update-traefik-config";
interface Props {
applicationId: string;

View File

@@ -105,7 +105,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
form.reset();
})
.catch(() => {
toast.error("Error to update the traefik config");
toast.error("Error updating the Traefik config");
});
};

View File

@@ -107,7 +107,7 @@ export const AddVolumes = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the Bind mount");
toast.error("Error creating the Bind mount");
});
} else if (data.type === "volume") {
await mutateAsync({
@@ -122,7 +122,7 @@ export const AddVolumes = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the Volume mount");
toast.error("Error creating the Volume mount");
});
} else if (data.type === "file") {
await mutateAsync({
@@ -138,7 +138,7 @@ export const AddVolumes = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to create the File mount");
toast.error("Error creating the File mount");
});
}

View File

@@ -1,61 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { TrashIcon } from "lucide-react";
import React from "react";
import { toast } from "sonner";
interface Props {
mountId: string;
refetch: () => void;
}
export const DeleteVolume = ({ mountId, refetch }: Props) => {
const { mutateAsync, isLoading } = api.mounts.remove.useMutation();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the mount
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
mountId,
})
.then(() => {
refetch();
toast.success("Mount deleted succesfully");
})
.catch(() => {
toast.error("Error to delete the mount");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -1,4 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
@@ -7,40 +9,48 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { Package } from "lucide-react";
import React from "react";
import { Package, Trash2 } from "lucide-react";
import { toast } from "sonner";
import type { ServiceType } from "../show-resources";
import { AddVolumes } from "./add-volumes";
import { DeleteVolume } from "./delete-volume";
import { UpdateVolume } from "./update-volume";
interface Props {
applicationId: string;
id: string;
type: ServiceType | "compose";
}
export const ShowVolumes = ({ applicationId }: Props) => {
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
},
{ enabled: !!applicationId },
);
export const ShowVolumes = ({ id, type }: Props) => {
const queryMap = {
postgres: () =>
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
mariadb: () =>
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
application: () =>
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
compose: () =>
api.compose.one.useQuery({ composeId: id }, { enabled: !!id }),
};
const { data, refetch } = queryMap[type]
? queryMap[type]()
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
const { mutateAsync: deleteVolume, isLoading: isRemoving } =
api.mounts.remove.useMutation();
return (
<Card className="bg-background">
<CardHeader className="flex flex-row justify-between flex-wrap gap-4">
<div>
<CardTitle className="text-xl">Volumes</CardTitle>
<CardDescription>
If you want to persist data in this application use the following
config to setup the volumes
If you want to persist data in this service use the following config
to setup the volumes
</CardDescription>
</div>
{data && data?.mounts.length > 0 && (
<AddVolumes
serviceId={applicationId}
refetch={refetch}
serviceType="application"
>
<AddVolumes serviceId={id} refetch={refetch} serviceType={type}>
Add Volume
</AddVolumes>
)}
@@ -52,17 +62,13 @@ export const ShowVolumes = ({ applicationId }: Props) => {
<span className="text-base text-muted-foreground">
No volumes/mounts configured
</span>
<AddVolumes
serviceId={applicationId}
refetch={refetch}
serviceType="application"
>
<AddVolumes serviceId={id} refetch={refetch} serviceType={type}>
Add Volume
</AddVolumes>
</div>
) : (
<div className="flex flex-col pt-2 gap-4">
<AlertBlock type="info">
<AlertBlock type="warning">
Please remember to click Redeploy after adding, editing, or
deleting a mount to apply the changes.
</AlertBlock>
@@ -73,7 +79,8 @@ export const ShowVolumes = ({ applicationId }: Props) => {
key={mount.mountId}
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 flex-col gap-4 sm:gap-8">
{/* <Package className="size-8 self-center text-muted-foreground" /> */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
<div className="flex flex-col gap-1">
<span className="font-medium">Mount Type</span>
<span className="text-sm text-muted-foreground">
@@ -90,21 +97,12 @@ export const ShowVolumes = ({ applicationId }: Props) => {
)}
{mount.type === "file" && (
<>
<div className="flex flex-col gap-1">
<span className="font-medium">Content</span>
<span className="text-sm text-muted-foreground">
{mount.content}
</span>
</div>
<div className="flex flex-col gap-1">
<span className="font-medium">File Path</span>
<span className="text-sm text-muted-foreground">
{mount.filePath}
</span>
</div>
</>
<div className="flex flex-col gap-1">
<span className="font-medium">Content</span>
<span className="text-sm text-muted-foreground line-clamp-[10] whitespace-break-spaces">
{mount.content}
</span>
</div>
)}
{mount.type === "bind" && (
<div className="flex flex-col gap-1">
@@ -114,21 +112,55 @@ export const ShowVolumes = ({ applicationId }: Props) => {
</span>
</div>
)}
<div className="flex flex-col gap-1">
<span className="font-medium">Mount Path</span>
<span className="text-sm text-muted-foreground">
{mount.mountPath}
</span>
</div>
{mount.type === "file" ? (
<div className="flex flex-col gap-1">
<span className="font-medium">File Path</span>
<span className="text-sm text-muted-foreground">
{mount.filePath}
</span>
</div>
) : (
<div className="flex flex-col gap-1">
<span className="font-medium">Mount Path</span>
<span className="text-sm text-muted-foreground">
{mount.mountPath}
</span>
</div>
)}
</div>
<div className="flex flex-row gap-1">
<UpdateVolume
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="application"
serviceType={type}
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
<DialogAction
title="Delete Volume"
description="Are you sure you want to delete this volume?"
type="destructive"
onClick={async () => {
await deleteVolume({
mountId: mount.mountId,
})
.then(() => {
refetch();
toast.success("Volume deleted successfully");
})
.catch(() => {
toast.error("Error deleting volume");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10"
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</div>
</div>
</div>

View File

@@ -21,7 +21,7 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Pencil } from "lucide-react";
import { PenBoxIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -77,7 +77,7 @@ export const UpdateVolume = ({
serviceType,
}: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const _utils = api.useUtils();
const { data } = api.mounts.one.useQuery(
{
mountId,
@@ -139,7 +139,7 @@ export const UpdateVolume = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the Bind mount");
toast.error("Error updating the Bind mount");
});
} else if (data.type === "volume") {
await mutateAsync({
@@ -153,7 +153,7 @@ export const UpdateVolume = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the Volume mount");
toast.error("Error updating the Volume mount");
});
} else if (data.type === "file") {
await mutateAsync({
@@ -168,7 +168,7 @@ export const UpdateVolume = ({
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the File mount");
toast.error("Error updating the File mount");
});
}
refetch();
@@ -177,8 +177,13 @@ export const UpdateVolume = ({
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<Pencil className="size-4 text-muted-foreground" />
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 "
isLoading={isLoading}
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-3xl">

View File

@@ -1,3 +1,4 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
@@ -25,6 +26,7 @@ enum BuildType {
paketo_buildpacks = "paketo_buildpacks",
nixpacks = "nixpacks",
static = "static",
railpack = "railpack",
}
const mySchema = z.discriminatedUnion("buildType", [
@@ -53,6 +55,9 @@ const mySchema = z.discriminatedUnion("buildType", [
z.object({
buildType: z.literal("static"),
}),
z.object({
buildType: z.literal("railpack"),
}),
]);
type AddTemplate = z.infer<typeof mySchema>;
@@ -126,7 +131,7 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the build type");
toast.error("Error saving the build type");
});
};
@@ -173,6 +178,15 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
Dockerfile
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="railpack" />
</FormControl>
<FormLabel className="font-normal">
Railpack{" "}
<Badge className="ml-1 text-xs px-1">New</Badge>
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="nixpacks" />

View File

@@ -1,161 +0,0 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { Copy, TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const deleteApplicationSchema = z.object({
projectName: z.string().min(1, {
message: "Application name is required",
}),
});
type DeleteApplication = z.infer<typeof deleteApplicationSchema>;
interface Props {
applicationId: string;
}
export const DeleteApplication = ({ applicationId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.application.delete.useMutation();
const { data } = api.application.one.useQuery(
{ applicationId },
{ enabled: !!applicationId },
);
const { push } = useRouter();
const form = useForm<DeleteApplication>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteApplicationSchema),
});
const onSubmit = async (formData: DeleteApplication) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({
applicationId,
})
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Application deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the application");
});
} else {
form.setError("projectName", {
message: "Project name does not match",
});
}
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
application. If you are sure please enter the application name to
delete this application.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-application"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center gap-2">
<span>
To confirm, type{" "}
<Badge
className="p-2 rounded-md ml-1 mr-1 hover:border-primary hover:text-primary-foreground hover:bg-primary hover:cursor-pointer"
variant="outline"
onClick={() => {
if (data?.name && data?.appName) {
navigator.clipboard.writeText(
`${data.name}/${data.appName}`,
);
toast.success("Copied to clipboard. Be careful!");
}
}}
>
{data?.name}/{data?.appName}&nbsp;
<Copy className="h-4 w-4 ml-1 text-muted-foreground" />
</Badge>{" "}
in the box below:
</span>
</FormLabel>
<FormControl>
<Input
placeholder="Enter application name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-application"
type="submit"
variant="destructive"
>
Confirm
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -20,6 +20,12 @@ interface Props {
export const CancelQueues = ({ applicationId }: Props) => {
const { mutateAsync, isLoading } = api.application.cleanQueues.useMutation();
const { data: isCloud } = api.settings.isCloud.useQuery();
if (isCloud) {
return null;
}
return (
<AlertDialog>
<AlertDialogTrigger asChild>

View File

@@ -11,7 +11,6 @@ import {
} from "@/components/ui/alert-dialog";
import { api } from "@/utils/api";
import { RefreshCcw } from "lucide-react";
import React from "react";
import { toast } from "sonner";
interface Props {
@@ -47,7 +46,7 @@ export const RefreshToken = ({ applicationId }: Props) => {
toast.success("Refresh updated");
})
.catch(() => {
toast.error("Error to update the refresh token");
toast.error("Error updating the refresh token");
});
}}
>

View File

@@ -17,8 +17,15 @@ interface Props {
open: boolean;
onClose: () => void;
serverId?: string;
errorMessage?: string;
}
export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
export const ShowDeployment = ({
logPath,
open,
onClose,
serverId,
errorMessage,
}: Props) => {
const [data, setData] = useState("");
const [showExtraLogs, setShowExtraLogs] = useState(false);
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
@@ -99,6 +106,8 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
}
}, [filteredLogs, autoScroll]);
const optionalErrors = parseLogs(errorMessage || "");
return (
<Dialog
open={open}
@@ -157,9 +166,17 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
<TerminalLine key={index} log={log} noTimestamp />
))
) : (
<div className="flex justify-center items-center h-full text-muted-foreground">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
<>
{optionalErrors.length > 0 ? (
optionalErrors.map((log: LogLine, index: number) => (
<TerminalLine key={`extra-${index}`} log={log} noTimestamp />
))
) : (
<div className="flex justify-center items-center h-full text-muted-foreground">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
)}
</>
)}
</div>
</DialogContent>

View File

@@ -8,7 +8,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { type RouterOutputs, api } from "@/utils/api";
import { RocketIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
import { CancelQueues } from "./cancel-queues";
@@ -18,8 +18,11 @@ import { ShowDeployment } from "./show-deployment";
interface Props {
applicationId: string;
}
export const ShowDeployments = ({ applicationId }: Props) => {
const [activeLog, setActiveLog] = useState<string | null>(null);
const [activeLog, setActiveLog] = useState<
RouterOutputs["deployment"]["all"][number] | null
>(null);
const { data } = api.application.one.useQuery({ applicationId });
const { data: deployments } = api.deployment.all.useQuery(
{ applicationId },
@@ -70,15 +73,14 @@ export const ShowDeployments = ({ applicationId }: Props) => {
</div>
) : (
<div className="flex flex-col gap-4">
{deployments?.map((deployment) => (
{deployments?.map((deployment, index) => (
<div
key={deployment.deploymentId}
className="flex items-center justify-between rounded-lg border p-4 gap-2"
>
<div className="flex flex-col">
<span className="flex items-center gap-4 font-medium capitalize text-foreground">
{deployment.status}
{index + 1}. {deployment.status}
<StatusTooltip
status={deployment?.status}
className="size-2.5"
@@ -100,7 +102,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
<Button
onClick={() => {
setActiveLog(deployment.logPath);
setActiveLog(deployment);
}}
>
View
@@ -112,9 +114,10 @@ export const ShowDeployments = ({ applicationId }: Props) => {
)}
<ShowDeployment
serverId={data?.serverId || ""}
open={activeLog !== null}
open={Boolean(activeLog && activeLog.logPath !== null)}
onClose={() => setActiveLog(null)}
logPath={activeLog}
logPath={activeLog?.logPath || ""}
errorMessage={activeLog?.errorMessage || ""}
/>
</CardContent>
</Card>

View File

@@ -85,8 +85,20 @@ export const AddDomain = ({
const form = useForm<Domain>({
resolver: zodResolver(domain),
defaultValues: {
host: "",
path: undefined,
port: undefined,
https: false,
certificateType: undefined,
customCertResolver: undefined,
},
mode: "onChange",
});
const certificateType = form.watch("certificateType");
const https = form.watch("https");
useEffect(() => {
if (data) {
form.reset({
@@ -94,19 +106,33 @@ export const AddDomain = ({
/* Convert null to undefined */
path: data?.path || undefined,
port: data?.port || undefined,
certificateType: data?.certificateType || undefined,
customCertResolver: data?.customCertResolver || undefined,
});
}
if (!domainId) {
form.reset({});
form.reset({
host: "",
path: undefined,
port: undefined,
https: false,
certificateType: undefined,
customCertResolver: undefined,
});
}
}, [form, form.reset, data, isLoading]);
}, [form, data, isLoading, domainId]);
// Separate effect for handling custom cert resolver validation
useEffect(() => {
if (certificateType === "custom") {
form.trigger("customCertResolver");
}
}, [certificateType, form]);
const dictionary = {
success: domainId ? "Domain Updated" : "Domain Created",
error: domainId
? "Error to update the domain"
: "Error to create the domain",
error: domainId ? "Error updating the domain" : "Error creating the domain",
submit: domainId ? "Update" : "Create",
dialogDescription: domainId
? "In this section you can edit a domain"
@@ -258,34 +284,73 @@ export const AddDomain = ({
)}
/>
{form.getValues().https && (
<FormField
control={form.control}
name="certificateType"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Certificate Provider</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a certificate provider" />
</SelectTrigger>
</FormControl>
{https && (
<>
<FormField
control={form.control}
name="certificateType"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Certificate Provider</FormLabel>
<Select
onValueChange={(value) => {
field.onChange(value);
if (value !== "custom") {
form.setValue(
"customCertResolver",
undefined,
);
}
}}
value={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a certificate provider" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value={"none"}>None</SelectItem>
<SelectItem value={"letsencrypt"}>
Let's Encrypt
</SelectItem>
<SelectItem value={"custom"}>Custom</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
);
}}
/>
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem value={"letsencrypt"}>
Let's Encrypt
</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
{certificateType === "custom" && (
<FormField
control={form.control}
name="customCertResolver"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Custom Certificate Resolver</FormLabel>
<FormControl>
<Input
className="w-full"
placeholder="Enter your custom certificate resolver"
{...field}
value={field.value || ""}
onChange={(e) => {
field.onChange(e);
form.trigger("customCertResolver");
}}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
)}
/>
</>
)}
</div>
</div>

View File

@@ -1,73 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { TrashIcon } from "lucide-react";
import React from "react";
import { toast } from "sonner";
interface Props {
domainId: string;
}
export const DeleteDomain = ({ domainId }: Props) => {
const { mutateAsync, isLoading } = api.domain.delete.useMutation();
const utils = api.useUtils();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
domain
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
domainId,
})
.then((data) => {
if (data?.applicationId) {
utils.domain.byApplicationId.invalidate({
applicationId: data?.applicationId,
});
utils.application.readTraefikConfig.invalidate({
applicationId: data?.applicationId,
});
} else if (data?.composeId) {
utils.domain.byComposeId.invalidate({
composeId: data?.composeId,
});
}
toast.success("Domain delete succesfully");
})
.catch(() => {
toast.error("Error to delete Domain");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -1,3 +1,4 @@
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -6,19 +7,18 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { ExternalLink, GlobeIcon, PenBoxIcon } from "lucide-react";
import { ExternalLink, GlobeIcon, PenBoxIcon, Trash2 } from "lucide-react";
import Link from "next/link";
import { toast } from "sonner";
import { AddDomain } from "./add-domain";
import { DeleteDomain } from "./delete-domain";
interface Props {
applicationId: string;
}
export const ShowDomains = ({ applicationId }: Props) => {
const { data } = api.domain.byApplicationId.useQuery(
const { data, refetch } = api.domain.byApplicationId.useQuery(
{
applicationId,
},
@@ -26,6 +26,10 @@ export const ShowDomains = ({ applicationId }: Props) => {
enabled: !!applicationId,
},
);
const { mutateAsync: deleteDomain, isLoading: isRemoving } =
api.domain.delete.useMutation();
return (
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
@@ -69,35 +73,66 @@ export const ShowDomains = ({ applicationId }: Props) => {
return (
<div
key={item.domainId}
className="flex w-full items-center gap-4 max-sm:flex-wrap border p-4 rounded-lg"
className="flex w-full items-center justify-between gap-4 border p-4 md:px-6 rounded-lg flex-wrap"
>
<Link
className="md:basis-1/2 flex gap-2 items-center hover:underline transition-all w-full"
target="_blank"
href={`${item.https ? "https" : "http"}://${item.host}${item.path}`}
>
<ExternalLink className="size-5" />
<span className="truncate max-w-full text-sm">
{item.host}
</span>
<ExternalLink className="size-4 min-w-4" />
</Link>
<Input disabled value={item.host} />
<Button variant="outline" disabled>
{item.path}
</Button>
<Button variant="outline" disabled>
{item.port}
</Button>
<Button variant="outline" disabled>
{item.https ? "HTTPS" : "HTTP"}
</Button>
<div className="flex flex-row gap-1">
<AddDomain
applicationId={applicationId}
domainId={item.domainId}
>
<Button variant="ghost">
<PenBoxIcon className="size-4 text-muted-foreground" />
</Button>
</AddDomain>
<DeleteDomain domainId={item.domainId} />
<div className="flex gap-8">
<div className="flex gap-8 opacity-50 items-center h-10 text-center text-sm font-medium">
<span>{item.path}</span>
<span>{item.port}</span>
<span>{item.https ? "HTTPS" : "HTTP"}</span>
</div>
<div className="flex gap-2">
<AddDomain
applicationId={applicationId}
domainId={item.domainId}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 "
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</AddDomain>
<DialogAction
title="Delete Domain"
description="Are you sure you want to delete this domain?"
type="destructive"
onClick={async () => {
await deleteDomain({
domainId: item.domainId,
})
.then(() => {
refetch();
toast.success("Domain deleted successfully");
})
.catch(() => {
toast.error("Error deleting domain");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10"
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</div>
</div>
</div>
);

View File

@@ -18,10 +18,11 @@ import { Toggle } from "@/components/ui/toggle";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { EyeIcon, EyeOffIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
import { type CSSProperties, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import type { ServiceType } from "../advanced/show-resources";
const addEnvironmentSchema = z.object({
environment: z.string(),
@@ -30,21 +31,39 @@ const addEnvironmentSchema = z.object({
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
interface Props {
composeId: string;
id: string;
type: Exclude<ServiceType | "compose", "application">;
}
export const ShowEnvironmentCompose = ({ composeId }: Props) => {
export const ShowEnvironment = ({ id, type }: Props) => {
const queryMap = {
postgres: () =>
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
mariadb: () =>
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
compose: () =>
api.compose.one.useQuery({ composeId: id }, { enabled: !!id }),
};
const { data, refetch } = queryMap[type]
? queryMap[type]()
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
const [isEnvVisible, setIsEnvVisible] = useState(true);
const { mutateAsync, isLoading } = api.compose.update.useMutation();
const { data, refetch } = api.compose.one.useQuery(
{
composeId,
},
{
enabled: !!composeId,
},
);
const mutationMap = {
postgres: () => api.postgres.update.useMutation(),
redis: () => api.redis.update.useMutation(),
mysql: () => api.mysql.update.useMutation(),
mariadb: () => api.mariadb.update.useMutation(),
mongo: () => api.mongo.update.useMutation(),
compose: () => api.compose.update.useMutation(),
};
const { mutateAsync, isLoading } = mutationMap[type]
? mutationMap[type]()
: api.mongo.update.useMutation();
const form = useForm<EnvironmentSchema>({
defaultValues: {
environment: "",
@@ -52,49 +71,42 @@ export const ShowEnvironmentCompose = ({ composeId }: Props) => {
resolver: zodResolver(addEnvironmentSchema),
});
// Watch form value
const currentEnvironment = form.watch("environment");
const hasChanges = currentEnvironment !== (data?.env || "");
useEffect(() => {
if (data) {
form.reset({
environment: data.env || "",
});
}
}, [form.reset, data, form]);
}, [data, form]);
const onSubmit = async (data: EnvironmentSchema) => {
const onSubmit = async (formData: EnvironmentSchema) => {
mutateAsync({
env: data.environment,
composeId,
mongoId: id || "",
postgresId: id || "",
redisId: id || "",
mysqlId: id || "",
mariadbId: id || "",
composeId: id || "",
env: formData.environment,
})
.then(async () => {
toast.success("Environments Added");
await refetch();
})
.catch(() => {
toast.error("Error to add environment");
toast.error("Error adding environment");
});
};
useEffect(() => {
if (isEnvVisible) {
if (data?.env) {
const maskedLines = data.env
.split("\n")
.map((line) => "*".repeat(line.length))
.join("\n");
form.reset({
environment: maskedLines,
});
} else {
form.reset({
environment: "",
});
}
} else {
form.reset({
environment: data?.env || "",
});
}
}, [form.reset, data, form, isEnvVisible]);
const handleCancel = () => {
form.reset({
environment: data?.env || "",
});
};
return (
<div className="flex w-full flex-col gap-5 ">
@@ -104,6 +116,11 @@ export const ShowEnvironmentCompose = ({ composeId }: Props) => {
<CardTitle className="text-xl">Environment Settings</CardTitle>
<CardDescription>
You can add environment variables to your resource.
{hasChanges && (
<span className="text-yellow-500 ml-2">
(You have unsaved changes)
</span>
)}
</CardDescription>
</div>
@@ -130,30 +147,44 @@ export const ShowEnvironmentCompose = ({ composeId }: Props) => {
control={form.control}
name="environment"
render={({ field }) => (
<FormItem className="w-full">
<FormControl>
<FormItem>
<FormControl className="">
<CodeEditor
style={
{
WebkitTextSecurity: isEnvVisible ? "disc" : null,
} as CSSProperties
}
language="properties"
disabled={isEnvVisible}
className="font-mono"
wrapperClassName="compose-file-editor"
placeholder={`NODE_ENV=production
PORT=3000
`}
className="h-96 font-mono"
`}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row justify-end">
<div className="flex flex-row justify-end gap-2">
{hasChanges && (
<Button
type="button"
variant="outline"
onClick={handleCancel}
>
Cancel
</Button>
)}
<Button
disabled={isEnvVisible}
isLoading={isLoading}
className="w-fit"
type="submit"
disabled={!hasChanges}
>
Save
</Button>

View File

@@ -1,5 +1,5 @@
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Card } from "@/components/ui/card";
import { Form } from "@/components/ui/form";
import { Secrets } from "@/components/ui/secrets";
import { api } from "@/utils/api";
@@ -7,6 +7,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { useEffect } from "react";
const addEnvironmentSchema = z.object({
env: z.string(),
@@ -34,16 +35,32 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
const form = useForm<EnvironmentSchema>({
defaultValues: {
env: data?.env || "",
buildArgs: data?.buildArgs || "",
env: "",
buildArgs: "",
},
resolver: zodResolver(addEnvironmentSchema),
});
const onSubmit = async (data: EnvironmentSchema) => {
// Watch form values
const currentEnv = form.watch("env");
const currentBuildArgs = form.watch("buildArgs");
const hasChanges =
currentEnv !== (data?.env || "") ||
currentBuildArgs !== (data?.buildArgs || "");
useEffect(() => {
if (data) {
form.reset({
env: data.env || "",
buildArgs: data.buildArgs || "",
});
}
}, [data, form]);
const onSubmit = async (formData: EnvironmentSchema) => {
mutateAsync({
env: data.env,
buildArgs: data.buildArgs,
env: formData.env,
buildArgs: formData.buildArgs,
applicationId,
})
.then(async () => {
@@ -51,21 +68,37 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to add environment");
toast.error("Error adding environment");
});
};
const handleCancel = () => {
form.reset({
env: data?.env || "",
buildArgs: data?.buildArgs || "",
});
};
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex w-full flex-col gap-5 "
>
<Card className="bg-background p-6">
<Card className="bg-background px-6 pb-6">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex w-full flex-col gap-4"
>
<Secrets
name="env"
title="Environment Settings"
description="You can add environment variables to your resource."
description={
<span>
You can add environment variables to your resource.
{hasChanges && (
<span className="text-yellow-500 ml-2">
(You have unsaved changes)
</span>
)}
</span>
}
placeholder={["NODE_ENV=production", "PORT=3000"].join("\n")}
/>
{data?.buildType === "dockerfile" && (
@@ -89,15 +122,23 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
placeholder="NPM_TOKEN=xyz"
/>
)}
<CardContent>
<div className="flex flex-row justify-end">
<Button isLoading={isLoading} className="w-fit" type="submit">
Save
<div className="flex flex-row justify-end gap-2">
{hasChanges && (
<Button type="button" variant="outline" onClick={handleCancel}>
Cancel
</Button>
</div>
</CardContent>
</Card>
</form>
</Form>
)}
<Button
isLoading={isLoading}
className="w-fit"
type="submit"
disabled={!hasChanges}
>
Save
</Button>
</div>
</form>
</Form>
</Card>
);
};

View File

@@ -1,74 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { useRouter } from "next/router";
import { toast } from "sonner";
interface Props {
applicationId: string;
}
export const DeployApplication = ({ applicationId }: Props) => {
const router = useRouter();
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
},
{ enabled: !!applicationId },
);
const { mutateAsync: deploy } = api.application.deploy.useMutation();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button isLoading={data?.applicationStatus === "running"}>
Deploy
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This will deploy the application
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await deploy({
applicationId,
})
.then(async () => {
toast.success("Application deployed succesfully");
await refetch();
router.push(
`/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`,
);
})
.catch(() => {
toast.error("Error to deploy Application");
});
await refetch();
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -29,14 +29,23 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown } from "lucide-react";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
import Link from "next/link";
const BitbucketProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
@@ -48,6 +57,7 @@ const BitbucketProviderSchema = z.object({
.required(),
branch: z.string().min(1, "Branch is required"),
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
watchPaths: z.array(z.string()).optional(),
});
type BitbucketProvider = z.infer<typeof BitbucketProviderSchema>;
@@ -73,6 +83,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
},
bitbucketId: "",
branch: "",
watchPaths: [],
},
resolver: zodResolver(BitbucketProviderSchema),
});
@@ -84,7 +95,6 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
data: repositories,
isLoading: isLoadingRepositories,
error,
isError,
} = api.bitbucket.getBitbucketRepositories.useQuery(
{
bitbucketId,
@@ -119,6 +129,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
},
buildPath: data.bitbucketBuildPath || "/",
bitbucketId: data.bitbucketId || "",
watchPaths: data.watchPaths || [],
});
}
}, [form.reset, data, form]);
@@ -131,13 +142,14 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
bitbucketBuildPath: data.buildPath,
bitbucketId: data.bitbucketId,
applicationId,
watchPaths: data.watchPaths || [],
})
.then(async () => {
toast.success("Service Provided Saved");
await refetch();
})
.catch(() => {
toast.error("Error to save the Bitbucket provider");
toast.error("Error saving the Bitbucket provider");
});
};
@@ -196,7 +208,20 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
name="repository"
render={({ field }) => (
<FormItem className="md:col-span-2 flex flex-col">
<FormLabel>Repository</FormLabel>
<div className="flex items-center justify-between">
<FormLabel>Repository</FormLabel>
{field.value.owner && field.value.repo && (
<Link
href={`https://bitbucket.org/${field.value.owner}/${field.value.repo}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
>
<BitbucketIcon className="h-4 w-4" />
<span>View Repository</span>
</Link>
)}
</div>
<Popover>
<PopoverTrigger asChild>
<FormControl>
@@ -235,7 +260,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
<CommandGroup>
{repositories?.map((repo) => (
<CommandItem
value={repo.url}
value={repo.name}
key={repo.url}
onSelect={() => {
form.setValue("repository", {
@@ -245,7 +270,12 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
form.setValue("branch", "");
}}
>
{repo.name}
<span className="flex items-center gap-2">
<span>{repo.name}</span>
<span className="text-muted-foreground text-xs">
{repo.owner.username}
</span>
</span>
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
@@ -359,6 +389,84 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="watchPaths"
render={({ field }) => (
<FormItem className="md:col-span-2">
<div className="flex items-center gap-2">
<FormLabel>Watch Paths</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
?
</div>
</TooltipTrigger>
<TooltipContent>
<p>
Add paths to watch for changes. When files in these
paths change, a new deployment will be triggered.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="flex flex-wrap gap-2 mb-2">
{field.value?.map((path, index) => (
<Badge key={index} variant="secondary">
{path}
<X
className="ml-1 size-3 cursor-pointer"
onClick={() => {
const newPaths = [...(field.value || [])];
newPaths.splice(index, 1);
form.setValue("watchPaths", newPaths);
}}
/>
</Badge>
))}
</div>
<FormControl>
<div className="flex gap-2">
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
const input = e.currentTarget;
const value = input.value.trim();
if (value) {
const newPaths = [...(field.value || []), value];
form.setValue("watchPaths", newPaths);
input.value = "";
}
}
}}
/>
<Button
type="button"
variant="secondary"
onClick={() => {
const input = document.querySelector(
'input[placeholder="Enter a path to watch (e.g., src/*, dist/*)"]',
) as HTMLInputElement;
const value = input.value.trim();
if (value) {
const newPaths = [...(field.value || []), value];
form.setValue("watchPaths", newPaths);
input.value = "";
}
}}
>
Add
</Button>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex w-full justify-end">
<Button

View File

@@ -68,7 +68,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the Docker provider");
toast.error("Error saving the Docker provider");
});
};
@@ -115,7 +115,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="username" {...field} />
<Input placeholder="Username" autoComplete="username" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -130,7 +130,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input placeholder="Password" {...field} type="password" />
<Input placeholder="Password" autoComplete="one-time-code" {...field} type="password" />
</FormControl>
<FormMessage />
</FormItem>

View File

@@ -56,7 +56,7 @@ export const SaveDragNDrop = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to save the deployment");
toast.error("Error saving the deployment");
});
};

View File

@@ -17,23 +17,33 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { KeyRoundIcon, LockIcon } from "lucide-react";
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
import { useRouter } from "next/router";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { GitIcon } from "@/components/icons/data-tools-icons";
const GitProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
repositoryURL: z.string().min(1, {
message: "Repository URL is required",
}),
branch: z.string().min(1, "Branch required"),
buildPath: z.string().min(1, "Build Path required"),
sshKey: z.string().optional(),
watchPaths: z.array(z.string()).optional(),
});
type GitProvider = z.infer<typeof GitProviderSchema>;
@@ -56,6 +66,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
buildPath: "/",
repositoryURL: "",
sshKey: undefined,
watchPaths: [],
},
resolver: zodResolver(GitProviderSchema),
});
@@ -67,6 +78,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
branch: data.customGitBranch || "",
buildPath: data.customGitBuildPath || "/",
repositoryURL: data.customGitUrl || "",
watchPaths: data.watchPaths || [],
});
}
}, [form.reset, data, form]);
@@ -78,13 +90,14 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
customGitUrl: values.repositoryURL,
customGitSSHKeyId: values.sshKey === "none" ? null : values.sshKey,
applicationId,
watchPaths: values.watchPaths || [],
})
.then(async () => {
toast.success("Git Provider Saved");
await refetch();
})
.catch(() => {
toast.error("Error to save the Git provider");
toast.error("Error saving the Git provider");
});
};
@@ -102,9 +115,22 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
name="repositoryURL"
render={({ field }) => (
<FormItem>
<FormLabel>Repository URL</FormLabel>
<div className="flex items-center justify-between">
<FormLabel>Repository URL</FormLabel>
{field.value?.startsWith("https://") && (
<Link
href={field.value}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
>
<GitIcon className="h-4 w-4" />
<span>View Repository</span>
</Link>
)}
</div>
<FormControl>
<Input placeholder="git@bitbucket.org" {...field} />
<Input placeholder="Repository URL" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -160,19 +186,22 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
</Button>
)}
</div>
<FormField
control={form.control}
name="branch"
render={({ field }) => (
<FormItem>
<FormLabel>Branch</FormLabel>
<FormControl>
<Input placeholder="Branch" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="space-y-4">
<FormField
control={form.control}
name="branch"
render={({ field }) => (
<FormItem>
<FormLabel>Branch</FormLabel>
<FormControl>
<Input placeholder="Branch" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="buildPath"
@@ -186,6 +215,85 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="watchPaths"
render={({ field }) => (
<FormItem className="md:col-span-2">
<div className="flex items-center gap-2">
<FormLabel>Watch Paths</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
?
</div>
</TooltipTrigger>
<TooltipContent className="max-w-[300px]">
<p>
Add paths to watch for changes. When files in these
paths change, a new deployment will be triggered. This
will work only when manual webhook is setup.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="flex flex-wrap gap-2 mb-2">
{field.value?.map((path, index) => (
<Badge key={index} variant="secondary">
{path}
<X
className="ml-1 size-3 cursor-pointer"
onClick={() => {
const newPaths = [...(field.value || [])];
newPaths.splice(index, 1);
form.setValue("watchPaths", newPaths);
}}
/>
</Badge>
))}
</div>
<FormControl>
<div className="flex gap-2">
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
const input = e.currentTarget;
const value = input.value.trim();
if (value) {
const newPaths = [...(field.value || []), value];
form.setValue("watchPaths", newPaths);
input.value = "";
}
}
}}
/>
<Button
type="button"
variant="secondary"
onClick={() => {
const input = document.querySelector(
'input[placeholder="Enter a path to watch (e.g., src/*, dist/*)"]',
) as HTMLInputElement;
const value = input.value.trim();
if (value) {
const newPaths = [...(field.value || []), value];
form.setValue("watchPaths", newPaths);
input.value = "";
}
}}
>
Add
</Button>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex flex-row justify-end">

View File

@@ -28,14 +28,23 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown } from "lucide-react";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import Link from "next/link";
import { GithubIcon } from "@/components/icons/data-tools-icons";
const GithubProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
@@ -47,6 +56,7 @@ const GithubProviderSchema = z.object({
.required(),
branch: z.string().min(1, "Branch is required"),
githubId: z.string().min(1, "Github Provider is required"),
watchPaths: z.array(z.string()).optional(),
});
type GithubProvider = z.infer<typeof GithubProviderSchema>;
@@ -113,6 +123,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
},
buildPath: data.buildPath || "/",
githubId: data.githubId || "",
watchPaths: data.watchPaths || [],
});
}
}, [form.reset, data, form]);
@@ -125,13 +136,14 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
owner: data.repository.owner,
buildPath: data.buildPath,
githubId: data.githubId,
watchPaths: data.watchPaths || [],
})
.then(async () => {
toast.success("Service Provided Saved");
await refetch();
})
.catch(() => {
toast.error("Error to save the github provider");
toast.error("Error saving the github provider");
});
};
@@ -187,7 +199,20 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
name="repository"
render={({ field }) => (
<FormItem className="md:col-span-2 flex flex-col">
<FormLabel>Repository</FormLabel>
<div className="flex items-center justify-between">
<FormLabel>Repository</FormLabel>
{field.value.owner && field.value.repo && (
<Link
href={`https://github.com/${field.value.owner}/${field.value.repo}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
>
<GithubIcon className="h-4 w-4" />
<span>View Repository</span>
</Link>
)}
</div>
<Popover>
<PopoverTrigger asChild>
<FormControl>
@@ -226,7 +251,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
<CommandGroup>
{repositories?.map((repo) => (
<CommandItem
value={repo.url}
value={repo.name}
key={repo.url}
onSelect={() => {
form.setValue("repository", {
@@ -236,7 +261,12 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
form.setValue("branch", "");
}}
>
{repo.name}
<span className="flex items-center gap-2">
<span>{repo.name}</span>
<span className="text-muted-foreground text-xs">
{repo.owner.login}
</span>
</span>
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
@@ -345,7 +375,85 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
<FormControl>
<Input placeholder="/" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="watchPaths"
render={({ field }) => (
<FormItem className="md:col-span-2">
<div className="flex items-center gap-2">
<FormLabel>Watch Paths</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipContent>
<p>
Add paths to watch for changes. When files in these
paths change, a new deployment will be triggered.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="flex flex-wrap gap-2 mb-2">
{field.value?.map((path, index) => (
<Badge
key={index}
variant="secondary"
className="flex items-center gap-1"
>
{path}
<X
className="size-3 cursor-pointer hover:text-destructive"
onClick={() => {
const newPaths = [...(field.value || [])];
newPaths.splice(index, 1);
field.onChange(newPaths);
}}
/>
</Badge>
))}
</div>
<div className="flex gap-2">
<FormControl>
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
const input = e.currentTarget;
const path = input.value.trim();
if (path) {
field.onChange([...(field.value || []), path]);
input.value = "";
}
}
}}
/>
</FormControl>
<Button
type="button"
variant="outline"
size="icon"
onClick={() => {
const input = document.querySelector(
'input[placeholder*="Enter a path"]',
) as HTMLInputElement;
const path = input.value.trim();
if (path) {
field.onChange([...(field.value || []), path]);
input.value = "";
}
}}
>
<Plus className="size-4" />
</Button>
</div>
<FormMessage />
</FormItem>
)}
@@ -360,6 +468,16 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
Save
</Button>
</div>
{/* create github link */}
<div className="flex w-full justify-end">
<Link
href={`https://github.com/${repository?.owner}/${repository?.repo}`}
target="_blank"
className="w-fit"
>
Repository
</Link>
</div>
</form>
</Form>
</div>

View File

@@ -29,14 +29,23 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown } from "lucide-react";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import Link from "next/link";
import { GitlabIcon } from "@/components/icons/data-tools-icons";
const GitlabProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),
@@ -50,6 +59,7 @@ const GitlabProviderSchema = z.object({
.required(),
branch: z.string().min(1, "Branch is required"),
gitlabId: z.string().min(1, "Gitlab Provider is required"),
watchPaths: z.array(z.string()).optional(),
});
type GitlabProvider = z.infer<typeof GitlabProviderSchema>;
@@ -124,6 +134,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
},
buildPath: data.gitlabBuildPath || "/",
gitlabId: data.gitlabId || "",
watchPaths: data.watchPaths || [],
});
}
}, [form.reset, data, form]);
@@ -138,13 +149,14 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
applicationId,
gitlabProjectId: data.repository.id,
gitlabPathNamespace: data.repository.gitlabPathNamespace,
watchPaths: data.watchPaths || [],
})
.then(async () => {
toast.success("Service Provided Saved");
await refetch();
})
.catch(() => {
toast.error("Error to save the gitlab provider");
toast.error("Error saving the gitlab provider");
});
};
@@ -203,7 +215,20 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
name="repository"
render={({ field }) => (
<FormItem className="md:col-span-2 flex flex-col">
<FormLabel>Repository</FormLabel>
<div className="flex items-center justify-between">
<FormLabel>Repository</FormLabel>
{field.value.owner && field.value.repo && (
<Link
href={`https://gitlab.com/${field.value.owner}/${field.value.repo}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
>
<GitlabIcon className="h-4 w-4" />
<span>View Repository</span>
</Link>
)}
</div>
<Popover>
<PopoverTrigger asChild>
<FormControl>
@@ -248,7 +273,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
{repositories?.map((repo) => {
return (
<CommandItem
value={repo.url}
value={repo.name}
key={repo.url}
onSelect={() => {
form.setValue("repository", {
@@ -260,7 +285,12 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
form.setValue("branch", "");
}}
>
{repo.name}
<span className="flex items-center gap-2">
<span>{repo.name}</span>
<span className="text-muted-foreground text-xs">
{repo.owner.username}
</span>
</span>
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
@@ -370,7 +400,85 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
<FormControl>
<Input placeholder="/" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="watchPaths"
render={({ field }) => (
<FormItem className="md:col-span-2">
<div className="flex items-center gap-2">
<FormLabel>Watch Paths</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipContent>
<p>
Add paths to watch for changes. When files in these
paths change, a new deployment will be triggered.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="flex flex-wrap gap-2 mb-2">
{field.value?.map((path, index) => (
<Badge
key={index}
variant="secondary"
className="flex items-center gap-1"
>
{path}
<X
className="size-3 cursor-pointer hover:text-destructive"
onClick={() => {
const newPaths = [...(field.value || [])];
newPaths.splice(index, 1);
field.onChange(newPaths);
}}
/>
</Badge>
))}
</div>
<div className="flex gap-2">
<FormControl>
<Input
placeholder="Enter a path to watch (e.g., src/*, dist/*)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
const input = e.currentTarget;
const path = input.value.trim();
if (path) {
field.onChange([...(field.value || []), path]);
input.value = "";
}
}
}}
/>
</FormControl>
<Button
type="button"
variant="outline"
size="icon"
onClick={() => {
const input = document.querySelector(
'input[placeholder*="Enter a path"]',
) as HTMLInputElement;
const path = input.value.trim();
if (path) {
field.onChange([...(field.value || []), path]);
input.value = "";
}
}}
>
<Plus className="size-4" />
</Button>
</div>
<FormMessage />
</FormItem>
)}

View File

@@ -11,7 +11,7 @@ import {
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { api } from "@/utils/api";
import { GitBranch, LockIcon, UploadCloud } from "lucide-react";
import { GitBranch, UploadCloud } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { SaveBitbucketProvider } from "./save-bitbucket-provider";

View File

@@ -1,70 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { RefreshCcw } from "lucide-react";
import { toast } from "sonner";
interface Props {
applicationId: string;
appName: string;
}
export const ResetApplication = ({ applicationId, appName }: Props) => {
const { refetch } = api.application.one.useQuery(
{
applicationId,
},
{ enabled: !!applicationId },
);
const { mutateAsync: reload, isLoading } =
api.application.reload.useMutation();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="secondary" isLoading={isLoading}>
Reload
<RefreshCcw className="size-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This will reload the application
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await reload({
applicationId,
appName,
})
.then(() => {
toast.success("Service Reloaded");
})
.catch(() => {
toast.error("Error to reload the service");
});
await refetch();
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -1,23 +1,34 @@
import { ShowBuildChooseForm } from "@/components/dashboard/application/build/show";
import { ShowProviderForm } from "@/components/dashboard/application/general/generic/show";
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
import { Terminal } from "lucide-react";
import React from "react";
import {
Ban,
CheckCircle2,
Hammer,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
import { RedbuildApplication } from "../rebuild-application";
import { StartApplication } from "../start-application";
import { StopApplication } from "../stop-application";
import { DeployApplication } from "./deploy-application";
import { ResetApplication } from "./reset-application";
interface Props {
applicationId: string;
}
export const ShowGeneralApplication = ({ applicationId }: Props) => {
const router = useRouter();
const { data, refetch } = api.application.one.useQuery(
{
applicationId,
@@ -25,6 +36,17 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
{ enabled: !!applicationId },
);
const { mutateAsync: update } = api.application.update.useMutation();
const { mutateAsync: start, isLoading: isStarting } =
api.application.start.useMutation();
const { mutateAsync: stop, isLoading: isStopping } =
api.application.stop.useMutation();
const { mutateAsync: deploy } = api.application.deploy.useMutation();
const { mutateAsync: reload, isLoading: isReloading } =
api.application.reload.useMutation();
const { mutateAsync: redeploy } = api.application.redeploy.useMutation();
return (
<>
@@ -33,18 +55,188 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
<CardTitle className="text-xl">Deploy Settings</CardTitle>
</CardHeader>
<CardContent className="flex flex-row gap-4 flex-wrap">
<DeployApplication applicationId={applicationId} />
<ResetApplication
applicationId={applicationId}
appName={data?.appName || ""}
/>
<TooltipProvider delayDuration={0}>
<DialogAction
title="Deploy Application"
description="Are you sure you want to deploy this application?"
type="default"
onClick={async () => {
await deploy({
applicationId: applicationId,
})
.then(() => {
toast.success("Application deployed successfully");
refetch();
router.push(
`/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`,
);
})
.catch(() => {
toast.error("Error deploying application");
});
}}
>
<Button
variant="default"
isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5"
>
Deploy
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>
Downloads the source code and performs a complete build
</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DialogAction>
<DialogAction
title="Reload Application"
description="Are you sure you want to reload this application?"
type="default"
onClick={async () => {
await reload({
applicationId: applicationId,
appName: data?.appName || "",
})
.then(() => {
toast.success("Application reloaded successfully");
refetch();
})
.catch(() => {
toast.error("Error reloading application");
});
}}
>
<Button variant="secondary" isLoading={isReloading}>
Reload
<RefreshCcw className="size-4" />
</Button>
</DialogAction>
<DialogAction
title="Rebuild Application"
description="Are you sure you want to rebuild this application?"
type="default"
onClick={async () => {
await redeploy({
applicationId: applicationId,
})
.then(() => {
toast.success("Application rebuilt successfully");
refetch();
})
.catch(() => {
toast.error("Error rebuilding application");
});
}}
>
<Button
variant="secondary"
isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5"
>
Rebuild
<Hammer className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>
Only rebuilds the application without downloading new
code
</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DialogAction>
<RedbuildApplication applicationId={applicationId} />
{data?.applicationStatus === "idle" ? (
<StartApplication applicationId={applicationId} />
) : (
<StopApplication applicationId={applicationId} />
)}
{data?.applicationStatus === "idle" ? (
<DialogAction
title="Start Application"
description="Are you sure you want to start this application?"
type="default"
onClick={async () => {
await start({
applicationId: applicationId,
})
.then(() => {
toast.success("Application started successfully");
refetch();
})
.catch(() => {
toast.error("Error starting application");
});
}}
>
<Button
variant="secondary"
isLoading={isStarting}
className="flex items-center gap-1.5"
>
Start
<CheckCircle2 className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>
Start the application (requires a previous successful
build)
</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DialogAction>
) : (
<DialogAction
title="Stop Application"
description="Are you sure you want to stop this application?"
onClick={async () => {
await stop({
applicationId: applicationId,
})
.then(() => {
toast.success("Application stopped successfully");
refetch();
})
.catch(() => {
toast.error("Error stopping application");
});
}}
>
<Button
variant="destructive"
isLoading={isStopping}
className="flex items-center gap-1.5"
>
Stop
<Ban className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Stop the currently running application</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DialogAction>
)}
</TooltipProvider>
<DockerTerminalModal
appName={data?.appName || ""}
serverId={data?.serverId || ""}
@@ -69,7 +261,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to update Auto Deploy");
toast.error("Error updating Auto Deploy");
});
}}
className="flex flex-row gap-2 items-center"

View File

@@ -94,6 +94,7 @@ export const AddPreviewDomain = ({
/* Convert null to undefined */
path: data?.path || undefined,
port: data?.port || undefined,
customCertResolver: data?.customCertResolver || undefined,
});
}
@@ -104,9 +105,7 @@ export const AddPreviewDomain = ({
const dictionary = {
success: domainId ? "Domain Updated" : "Domain Created",
error: domainId
? "Error to update the domain"
: "Error to create the domain",
error: domainId ? "Error updating the domain" : "Error creating the domain",
submit: domainId ? "Update" : "Create",
dialogDescription: domainId
? "In this section you can edit a domain"

View File

@@ -5,7 +5,6 @@ import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
@@ -26,7 +25,9 @@ export const ShowPreviewBuilds = ({
serverId,
trigger,
}: Props) => {
const [activeLog, setActiveLog] = useState<string | null>(null);
const [activeLog, setActiveLog] = useState<
RouterOutputs["deployment"]["all"][number] | null
>(null);
const [isOpen, setIsOpen] = useState(false);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
@@ -77,7 +78,7 @@ export const ShowPreviewBuilds = ({
<Button
onClick={() => {
setActiveLog(deployment.logPath);
setActiveLog(deployment);
}}
>
View
@@ -89,9 +90,10 @@ export const ShowPreviewBuilds = ({
</DialogContent>
<ShowDeployment
serverId={serverId || ""}
open={activeLog !== null}
open={Boolean(activeLog && activeLog.logPath !== null)}
onClose={() => setActiveLog(null)}
logPath={activeLog}
logPath={activeLog?.logPath || ""}
errorMessage={activeLog?.errorMessage || ""}
/>
</Dialog>
);

View File

@@ -22,7 +22,6 @@ import {
RocketIcon,
Trash2,
} from "lucide-react";
import React from "react";
import { toast } from "sonner";
import { ShowModalLogs } from "../../settings/web-server/show-modal-logs";
import { AddPreviewDomain } from "./add-preview-domain";

View File

@@ -35,16 +35,30 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const schema = z.object({
env: z.string(),
buildArgs: z.string(),
wildcardDomain: z.string(),
port: z.number(),
previewLimit: z.number(),
previewHttps: z.boolean(),
previewPath: z.string(),
previewCertificateType: z.enum(["letsencrypt", "none"]),
});
const schema = z
.object({
env: z.string(),
buildArgs: z.string(),
wildcardDomain: z.string(),
port: z.number(),
previewLimit: z.number(),
previewHttps: z.boolean(),
previewPath: z.string(),
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
previewCustomCertResolver: z.string().optional(),
})
.superRefine((input, ctx) => {
if (
input.previewCertificateType === "custom" &&
!input.previewCustomCertResolver
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["previewCustomCertResolver"],
message: "Required",
});
}
});
type Schema = z.infer<typeof schema>;
@@ -90,6 +104,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewHttps: data.previewHttps || false,
previewPath: data.previewPath || "/",
previewCertificateType: data.previewCertificateType || "none",
previewCustomCertResolver: data.previewCustomCertResolver || "",
});
}
}, [data]);
@@ -105,6 +120,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewHttps: formData.previewHttps,
previewPath: formData.previewPath,
previewCertificateType: formData.previewCertificateType,
previewCustomCertResolver: formData.previewCustomCertResolver,
})
.then(() => {
toast.success("Preview Deployments settings updated");
@@ -184,10 +200,6 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
render={({ field }) => (
<FormItem>
<FormLabel>Preview Limit</FormLabel>
{/* <FormDescription>
Set the limit of preview deployments that can be
created for this app.
</FormDescription> */}
<FormControl>
<NumberInput placeholder="3000" {...field} />
</FormControl>
@@ -238,6 +250,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
<SelectItem value={"letsencrypt"}>
Let's Encrypt
</SelectItem>
<SelectItem value={"custom"}>Custom</SelectItem>
</SelectContent>
</Select>
<FormMessage />
@@ -245,6 +258,25 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
)}
/>
)}
{form.watch("previewCertificateType") === "custom" && (
<FormField
control={form.control}
name="previewCustomCertResolver"
render={({ field }) => (
<FormItem>
<FormLabel>Certificate Provider</FormLabel>
<FormControl>
<Input
placeholder="my-custom-resolver"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
</div>
<div className="grid gap-4 lg:grid-cols-2">
<div className="flex flex-row items-center justify-between rounded-lg border p-4 col-span-2">
@@ -279,7 +311,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
<FormField
control={form.control}
name="env"
render={({ field }) => (
render={() => (
<FormItem>
<FormControl>
<Secrets
@@ -291,16 +323,6 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
"PORT=3000",
].join("\n")}
/>
{/* <CodeEditor
lineWrapping
language="properties"
wrapperClassName="h-[25rem] font-mono"
placeholder={`NODE_ENV=production
PORT=3000
`}
{...field}
/> */}
</FormControl>
<FormMessage />
</FormItem>

View File

@@ -1,76 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { Hammer } from "lucide-react";
import { toast } from "sonner";
interface Props {
applicationId: string;
}
export const RedbuildApplication = ({ applicationId }: Props) => {
const { data } = api.application.one.useQuery(
{
applicationId,
},
{ enabled: !!applicationId },
);
const { mutateAsync } = api.application.redeploy.useMutation();
const utils = api.useUtils();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="secondary"
isLoading={data?.applicationStatus === "running"}
>
Rebuild
<Hammer className="size-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure to rebuild the application?
</AlertDialogTitle>
<AlertDialogDescription>
Is required to deploy at least 1 time in order to reuse the same
code
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
toast.success("Redeploying Application....");
await mutateAsync({
applicationId,
})
.then(async () => {
await utils.application.one.invalidate({
applicationId,
});
})
.catch(() => {
toast.error("Error to rebuild the application");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -1,65 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { CheckCircle2 } from "lucide-react";
import { toast } from "sonner";
interface Props {
applicationId: string;
}
export const StartApplication = ({ applicationId }: Props) => {
const { mutateAsync, isLoading } = api.application.start.useMutation();
const utils = api.useUtils();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="secondary" isLoading={isLoading}>
Start
<CheckCircle2 className="size-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure to start the application?
</AlertDialogTitle>
<AlertDialogDescription>
This will start the application
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
applicationId,
})
.then(async () => {
await utils.application.one.invalidate({
applicationId,
});
toast.success("Application started succesfully");
})
.catch(() => {
toast.error("Error to start the Application");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -1,65 +0,0 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { Ban } from "lucide-react";
import { toast } from "sonner";
interface Props {
applicationId: string;
}
export const StopApplication = ({ applicationId }: Props) => {
const { mutateAsync, isLoading } = api.application.stop.useMutation();
const utils = api.useUtils();
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" isLoading={isLoading}>
Stop
<Ban className="size-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you absolutely sure to stop the application?
</AlertDialogTitle>
<AlertDialogDescription>
This will stop the application
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
applicationId,
})
.then(async () => {
await utils.application.one.invalidate({
applicationId,
});
toast.success("Application stopped succesfully");
})
.catch(() => {
toast.error("Error to stop the Application");
});
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};

View File

@@ -21,7 +21,7 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, SquarePen } from "lucide-react";
import { PenBoxIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -76,14 +76,14 @@ export const UpdateApplication = ({ applicationId }: Props) => {
description: formData.description || "",
})
.then(() => {
toast.success("Application updated succesfully");
toast.success("Application updated successfully");
utils.application.one.invalidate({
applicationId: applicationId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update the application");
toast.error("Error updating the Application");
})
.finally(() => {});
};
@@ -91,8 +91,12 @@ export const UpdateApplication = ({ applicationId }: Props) => {
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost">
<SquarePen className="size-4 text-muted-foreground" />
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 "
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">

View File

@@ -19,7 +19,6 @@ import {
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import React from "react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -82,7 +81,7 @@ export const AddCommandCompose = ({ composeId }: Props) => {
});
})
.catch(() => {
toast.error("Error to update the command");
toast.error("Error updating the command");
});
};

View File

@@ -1,142 +0,0 @@
import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { Package } from "lucide-react";
import React from "react";
import { AddVolumes } from "../../application/advanced/volumes/add-volumes";
import { DeleteVolume } from "../../application/advanced/volumes/delete-volume";
import { UpdateVolume } from "../../application/advanced/volumes/update-volume";
interface Props {
composeId: string;
}
export const ShowVolumesCompose = ({ composeId }: Props) => {
const { data, refetch } = api.compose.one.useQuery(
{
composeId,
},
{ enabled: !!composeId },
);
return (
<Card className="bg-background">
<CardHeader className="flex flex-row justify-between flex-wrap gap-4">
<div>
<CardTitle className="text-xl">Volumes</CardTitle>
<CardDescription>
If you want to persist data in this compose use the following config
to setup the volumes
</CardDescription>
</div>
{data && data?.mounts.length > 0 && (
<AddVolumes
serviceId={composeId}
refetch={refetch}
serviceType="compose"
>
Add Volume
</AddVolumes>
)}
</CardHeader>
<CardContent className="flex flex-col gap-4">
{data?.mounts.length === 0 ? (
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
<Package className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
No volumes/mounts configured
</span>
<AddVolumes
serviceId={composeId}
refetch={refetch}
serviceType="compose"
>
Add Volume
</AddVolumes>
</div>
) : (
<div className="flex flex-col pt-2 gap-4">
<AlertBlock type="info">
Please remember to click Redeploy after adding, editing, or
deleting a mount to apply the changes.
</AlertBlock>
<div className="flex flex-col gap-6">
{data?.mounts.map((mount) => (
<div key={mount.mountId}>
<div
key={mount.mountId}
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 flex-col gap-4 sm:gap-8">
<div className="flex flex-col gap-1">
<span className="font-medium">Mount Type</span>
<span className="text-sm text-muted-foreground">
{mount.type.toUpperCase()}
</span>
</div>
{mount.type === "volume" && (
<div className="flex flex-col gap-1">
<span className="font-medium">Volume Name</span>
<span className="text-sm text-muted-foreground">
{mount.volumeName}
</span>
</div>
)}
{mount.type === "file" && (
<>
<div className="flex flex-col gap-1">
<span className="font-medium">Content</span>
<span className="text-sm text-muted-foreground w-40 truncate">
{mount.content}
</span>
</div>
<div className="flex flex-col gap-1">
<span className="font-medium">File Path</span>
<span className="text-sm text-muted-foreground">
{mount.filePath}
</span>
</div>
</>
)}
{mount.type === "bind" && (
<div className="flex flex-col gap-1">
<span className="font-medium">Host Path</span>
<span className="text-sm text-muted-foreground">
{mount.hostPath}
</span>
</div>
)}
<div className="flex flex-col gap-1">
<span className="font-medium">Mount Path</span>
<span className="text-sm text-muted-foreground">
{mount.mountPath}
</span>
</div>
</div>
<div className="flex flex-row gap-1">
<UpdateVolume
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="compose"
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
</div>
</div>
</div>
))}
</div>
</div>
)}
</CardContent>
</Card>
);
};

View File

@@ -13,7 +13,6 @@ import {
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
@@ -21,9 +20,10 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import type { ServiceType } from "@dokploy/server/db/schema";
import { zodResolver } from "@hookform/resolvers/zod";
import { Copy } from "lucide-react";
import { TrashIcon } from "lucide-react";
import copy from "copy-to-clipboard";
import { Copy, Trash2 } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
@@ -40,16 +40,42 @@ const deleteComposeSchema = z.object({
type DeleteCompose = z.infer<typeof deleteComposeSchema>;
interface Props {
composeId: string;
id: string;
type: ServiceType | "application";
}
export const DeleteCompose = ({ composeId }: Props) => {
export const DeleteService = ({ id, type }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.compose.delete.useMutation();
const { data } = api.compose.one.useQuery(
{ composeId },
{ enabled: !!composeId },
);
const queryMap = {
postgres: () =>
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
mariadb: () =>
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
application: () =>
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
compose: () =>
api.compose.one.useQuery({ composeId: id }, { enabled: !!id }),
};
const { data } = queryMap[type]
? queryMap[type]()
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
const mutationMap = {
postgres: () => api.postgres.remove.useMutation(),
redis: () => api.redis.remove.useMutation(),
mysql: () => api.mysql.remove.useMutation(),
mariadb: () => api.mariadb.remove.useMutation(),
application: () => api.application.delete.useMutation(),
mongo: () => api.mongo.remove.useMutation(),
compose: () => api.compose.delete.useMutation(),
};
const { mutateAsync, isLoading } = mutationMap[type]
? mutationMap[type]()
: api.mongo.remove.useMutation();
const { push } = useRouter();
const form = useForm<DeleteCompose>({
defaultValues: {
@@ -63,14 +89,23 @@ export const DeleteCompose = ({ composeId }: Props) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
const { deleteVolumes } = formData;
await mutateAsync({ composeId, deleteVolumes })
await mutateAsync({
mongoId: id || "",
postgresId: id || "",
redisId: id || "",
mysqlId: id || "",
mariadbId: id || "",
applicationId: id || "",
composeId: id || "",
deleteVolumes,
})
.then((result) => {
push(`/dashboard/project/${result?.projectId}`);
toast.success("Compose deleted successfully");
toast.success("deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the compose");
toast.error("Error deleting the service");
});
} else {
form.setError("projectName", {
@@ -82,8 +117,13 @@ export const DeleteCompose = ({ composeId }: Props) => {
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" />
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10 "
isLoading={isLoading}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
@@ -91,8 +131,8 @@ export const DeleteCompose = ({ composeId }: Props) => {
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
compose. If you are sure please enter the compose name to delete
this compose.
service. If you are sure please enter the service name to delete
this service.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
@@ -115,9 +155,7 @@ export const DeleteCompose = ({ composeId }: Props) => {
variant="outline"
onClick={() => {
if (data?.name && data?.appName) {
navigator.clipboard.writeText(
`${data.name}/${data.appName}`,
);
copy(`${data.name}/${data.appName}`);
toast.success("Copied to clipboard. Be careful!");
}
}}
@@ -138,27 +176,29 @@ export const DeleteCompose = ({ composeId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="deleteVolumes"
render={({ field }) => (
<FormItem>
<div className="flex items-center">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
{type === "compose" && (
<FormField
control={form.control}
name="deleteVolumes"
render={({ field }) => (
<FormItem>
<div className="flex items-center">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel className="ml-2">
Delete volumes associated with this compose
</FormLabel>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormLabel className="ml-2">
Delete volumes associated with this compose
</FormLabel>
</div>
<FormMessage />
</FormItem>
)}
/>
)}
</form>
</Form>
</div>

View File

@@ -20,6 +20,11 @@ interface Props {
export const CancelQueuesCompose = ({ composeId }: Props) => {
const { mutateAsync, isLoading } = api.compose.cleanQueues.useMutation();
const { data: isCloud } = api.settings.isCloud.useQuery();
if (isCloud) {
return null;
}
return (
<AlertDialog>
<AlertDialogTrigger asChild>

View File

@@ -11,7 +11,6 @@ import {
} from "@/components/ui/alert-dialog";
import { api } from "@/utils/api";
import { RefreshCcw } from "lucide-react";
import React from "react";
import { toast } from "sonner";
interface Props {
@@ -47,7 +46,7 @@ export const RefreshTokenCompose = ({ composeId }: Props) => {
toast.success("Refresh Token updated");
})
.catch(() => {
toast.error("Error to update the refresh token");
toast.error("Error updating the refresh token");
});
}}
>

View File

@@ -17,12 +17,14 @@ interface Props {
serverId?: string;
open: boolean;
onClose: () => void;
errorMessage?: string;
}
export const ShowDeploymentCompose = ({
logPath,
open,
onClose,
serverId,
errorMessage,
}: Props) => {
const [data, setData] = useState("");
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
@@ -105,6 +107,8 @@ export const ShowDeploymentCompose = ({
}
}, [filteredLogs, autoScroll]);
const optionalErrors = parseLogs(errorMessage || "");
return (
<Dialog
open={open}
@@ -161,9 +165,17 @@ export const ShowDeploymentCompose = ({
<TerminalLine key={index} log={log} noTimestamp />
))
) : (
<div className="flex justify-center items-center h-full text-muted-foreground">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
<>
{optionalErrors.length > 0 ? (
optionalErrors.map((log: LogLine, index: number) => (
<TerminalLine key={`extra-${index}`} log={log} noTimestamp />
))
) : (
<div className="flex justify-center items-center h-full text-muted-foreground">
<Loader2 className="h-6 w-6 animate-spin" />
</div>
)}
</>
)}
</div>
</DialogContent>

View File

@@ -8,7 +8,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { type RouterOutputs, api } from "@/utils/api";
import { RocketIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
import { CancelQueuesCompose } from "./cancel-queues-compose";
@@ -19,7 +19,9 @@ interface Props {
composeId: string;
}
export const ShowDeploymentsCompose = ({ composeId }: Props) => {
const [activeLog, setActiveLog] = useState<string | null>(null);
const [activeLog, setActiveLog] = useState<
RouterOutputs["deployment"]["all"][number] | null
>(null);
const { data } = api.compose.one.useQuery({ composeId });
const { data: deployments } = api.deployment.allByCompose.useQuery(
{ composeId },
@@ -100,7 +102,7 @@ export const ShowDeploymentsCompose = ({ composeId }: Props) => {
<Button
onClick={() => {
setActiveLog(deployment.logPath);
setActiveLog(deployment);
}}
>
View
@@ -112,9 +114,10 @@ export const ShowDeploymentsCompose = ({ composeId }: Props) => {
)}
<ShowDeploymentCompose
serverId={data?.serverId || ""}
open={activeLog !== null}
open={Boolean(activeLog && activeLog.logPath !== null)}
onClose={() => setActiveLog(null)}
logPath={activeLog}
logPath={activeLog?.logPath || ""}
errorMessage={activeLog?.errorMessage || ""}
/>
</CardContent>
</Card>

View File

@@ -104,6 +104,15 @@ export const AddDomainCompose = ({
const form = useForm<Domain>({
resolver: zodResolver(domainCompose),
defaultValues: {
host: "",
path: undefined,
port: undefined,
https: false,
certificateType: undefined,
customCertResolver: undefined,
serviceName: "",
},
});
const https = form.watch("https");
@@ -116,19 +125,27 @@ export const AddDomainCompose = ({
path: data?.path || undefined,
port: data?.port || undefined,
serviceName: data?.serviceName || undefined,
certificateType: data?.certificateType || undefined,
customCertResolver: data?.customCertResolver || undefined,
});
}
if (!domainId) {
form.reset({});
form.reset({
host: "",
path: undefined,
port: undefined,
https: false,
certificateType: undefined,
customCertResolver: undefined,
serviceName: "",
});
}
}, [form, form.reset, data, isLoading]);
const dictionary = {
success: domainId ? "Domain Updated" : "Domain Created",
error: domainId
? "Error to update the domain"
: "Error to create the domain",
error: domainId ? "Error updating the domain" : "Error creating the domain",
submit: domainId ? "Update" : "Create",
dialogDescription: domainId
? "In this section you can edit a domain"
@@ -395,33 +412,55 @@ export const AddDomainCompose = ({
/>
{https && (
<FormField
control={form.control}
name="certificateType"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Certificate Provider</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a certificate provider" />
</SelectTrigger>
</FormControl>
<>
<FormField
control={form.control}
name="certificateType"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Certificate Provider</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a certificate provider" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem value={"letsencrypt"}>
Let's Encrypt
</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem value={"letsencrypt"}>
Let's Encrypt
</SelectItem>
<SelectItem value={"custom"}>Custom</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
{form.getValues().certificateType === "custom" && (
<FormField
control={form.control}
name="customCertResolver"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Custom Certificate Resolver</FormLabel>
<FormControl>
<Input
placeholder="Enter your custom certificate resolver"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
/>
</>
)}
</div>
</div>

View File

@@ -1,3 +1,4 @@
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -6,11 +7,10 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { ExternalLink, GlobeIcon, PenBoxIcon } from "lucide-react";
import { ExternalLink, GlobeIcon, PenBoxIcon, Trash2 } from "lucide-react";
import Link from "next/link";
import { DeleteDomain } from "../../application/domains/delete-domain";
import { toast } from "sonner";
import { AddDomainCompose } from "./add-domain";
interface Props {
@@ -18,7 +18,7 @@ interface Props {
}
export const ShowDomainsCompose = ({ composeId }: Props) => {
const { data } = api.domain.byComposeId.useQuery(
const { data, refetch } = api.domain.byComposeId.useQuery(
{
composeId,
},
@@ -27,6 +27,9 @@ export const ShowDomainsCompose = ({ composeId }: Props) => {
},
);
const { mutateAsync: deleteDomain, isLoading: isRemoving } =
api.domain.delete.useMutation();
return (
<div className="flex w-full flex-col gap-5 ">
<Card className="bg-background">
@@ -70,34 +73,70 @@ export const ShowDomainsCompose = ({ composeId }: Props) => {
return (
<div
key={item.domainId}
className="flex w-full items-center gap-4 max-sm:flex-wrap border p-4 rounded-lg"
className="flex w-full items-center justify-between gap-4 border p-4 md:px-6 rounded-lg flex-wrap"
>
<Link target="_blank" href={`http://${item.host}`}>
<ExternalLink className="size-5" />
</Link>
<Button variant="outline" disabled>
{item.serviceName}
</Button>
<Input disabled value={item.host} />
<Button variant="outline" disabled>
{item.path}
</Button>
<Button variant="outline" disabled>
{item.port}
</Button>
<Button variant="outline" disabled>
{item.https ? "HTTPS" : "HTTP"}
</Button>
<div className="flex flex-row gap-1">
<AddDomainCompose
composeId={composeId}
domainId={item.domainId}
<div className="md:basis-1/2 flex gap-6 w-full items-center">
<span className="opacity-50 text-center font-medium text-sm whitespace-nowrap">
{item.serviceName}
</span>
<Link
className="flex gap-2 items-center hover:underline transition-all w-full max-w-[calc(100%-4rem)]"
target="_blank"
href={`${item.https ? "https" : "http"}://${item.host}${item.path}`}
>
<Button variant="ghost">
<PenBoxIcon className="size-4 text-muted-foreground" />
</Button>
</AddDomainCompose>
<DeleteDomain domainId={item.domainId} />
<span className="truncate text-sm">{item.host}</span>
<ExternalLink className="size-4 min-w-4" />
</Link>
</div>
<div className="flex gap-8">
<div className="flex gap-8 opacity-50 items-center h-10 text-center text-sm font-medium">
<span>{item.path}</span>
<span>{item.port}</span>
<span>{item.https ? "HTTPS" : "HTTP"}</span>
</div>
<div className="flex gap-2">
<AddDomainCompose
composeId={composeId}
domainId={item.domainId}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 "
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</AddDomainCompose>
<DialogAction
title="Delete Domain"
description="Are you sure you want to delete this domain?"
type="destructive"
onClick={async () => {
await deleteDomain({
domainId: item.domainId,
})
.then((_data) => {
refetch();
toast.success("Domain deleted successfully");
})
.catch(() => {
toast.error("Error deleting domain");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10"
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</div>
</div>
</div>
);

View File

@@ -1,28 +1,24 @@
import { DialogAction } from "@/components/shared/dialog-action";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
import { CheckCircle2, ExternalLink, Globe, Terminal } from "lucide-react";
import Link from "next/link";
import { Ban, CheckCircle2, Hammer, HelpCircle, Terminal } from "lucide-react";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
import { StartCompose } from "../start-compose";
import { DeployCompose } from "./deploy-compose";
import { RedbuildCompose } from "./rebuild-compose";
import { StopCompose } from "./stop-compose";
interface Props {
composeId: string;
}
export const ComposeActions = ({ composeId }: Props) => {
const router = useRouter();
const { data, refetch } = api.compose.one.useQuery(
{
composeId,
@@ -30,35 +26,167 @@ export const ComposeActions = ({ composeId }: Props) => {
{ enabled: !!composeId },
);
const { mutateAsync: update } = api.compose.update.useMutation();
const extractDomains = (env: string) => {
const lines = env.split("\n");
const hostLines = lines.filter((line) => {
const [key, value] = line.split("=");
return key?.trim().endsWith("_HOST");
});
const hosts = hostLines.map((line) => {
const [key, value] = line.split("=");
return value ? value.trim() : "";
});
return hosts;
};
const domains = extractDomains(data?.env || "");
const { mutateAsync: deploy } = api.compose.deploy.useMutation();
const { mutateAsync: redeploy } = api.compose.redeploy.useMutation();
const { mutateAsync: start, isLoading: isStarting } =
api.compose.start.useMutation();
const { mutateAsync: stop, isLoading: isStopping } =
api.compose.stop.useMutation();
return (
<div className="flex flex-row gap-4 w-full flex-wrap ">
<DeployCompose composeId={composeId} />
<RedbuildCompose composeId={composeId} />
{data?.composeType === "docker-compose" &&
data?.composeStatus === "idle" ? (
<StartCompose composeId={composeId} />
) : (
<StopCompose composeId={composeId} />
)}
<TooltipProvider delayDuration={0}>
<DialogAction
title="Deploy Compose"
description="Are you sure you want to deploy this compose?"
type="default"
onClick={async () => {
await deploy({
composeId: composeId,
})
.then(() => {
toast.success("Compose deployed successfully");
refetch();
router.push(
`/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`,
);
})
.catch(() => {
toast.error("Error deploying compose");
});
}}
>
<Button
variant="default"
isLoading={data?.composeStatus === "running"}
className="flex items-center gap-1.5"
>
Deploy
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Downloads the source code and performs a complete build</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DialogAction>
<DialogAction
title="Rebuild Compose"
description="Are you sure you want to rebuild this compose?"
type="default"
onClick={async () => {
await redeploy({
composeId: composeId,
})
.then(() => {
toast.success("Compose rebuilt successfully");
refetch();
})
.catch(() => {
toast.error("Error rebuilding compose");
});
}}
>
<Button
variant="secondary"
isLoading={data?.composeStatus === "running"}
className="flex items-center gap-1.5"
>
Rebuild
<Hammer className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Only rebuilds the compose without downloading new code</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DialogAction>
{data?.composeType === "docker-compose" &&
data?.composeStatus === "idle" ? (
<DialogAction
title="Start Compose"
description="Are you sure you want to start this compose?"
type="default"
onClick={async () => {
await start({
composeId: composeId,
})
.then(() => {
toast.success("Compose started successfully");
refetch();
})
.catch(() => {
toast.error("Error starting compose");
});
}}
>
<Button
variant="secondary"
isLoading={isStarting}
className="flex items-center gap-1.5"
>
Start
<CheckCircle2 className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>
Start the compose (requires a previous successful build)
</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DialogAction>
) : (
<DialogAction
title="Stop Compose"
description="Are you sure you want to stop this compose?"
onClick={async () => {
await stop({
composeId: composeId,
})
.then(() => {
toast.success("Compose stopped successfully");
refetch();
})
.catch(() => {
toast.error("Error stopping compose");
});
}}
>
<Button
variant="destructive"
isLoading={isStopping}
className="flex items-center gap-1.5"
>
Stop
<Ban className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Stop the currently running compose</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DialogAction>
)}
</TooltipProvider>
<DockerTerminalModal
appName={data?.appName || ""}
serverId={data?.serverId || ""}
@@ -83,47 +211,12 @@ export const ComposeActions = ({ composeId }: Props) => {
await refetch();
})
.catch(() => {
toast.error("Error to update Auto Deploy");
toast.error("Error updating Auto Deploy");
});
}}
className="flex flex-row gap-2 items-center"
/>
</div>
{domains.length > 0 && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
Domains
<Globe className="text-xs size-4 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Domains detected</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
{domains.map((host, index) => {
const url =
host.startsWith("http://") || host.startsWith("https://")
? host
: `http://${host}`;
return (
<DropdownMenuItem
key={`domain-${index}`}
className="cursor-pointer"
asChild
>
<Link href={url} target="_blank">
{host}
<ExternalLink className="ml-2 text-xs text-muted-foreground" />
</Link>
</DropdownMenuItem>
);
})}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
);
};

View File

@@ -14,7 +14,7 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
import { RandomizeCompose } from "./randomize-compose";
import { ShowUtilities } from "./show-utilities";
interface Props {
composeId: string;
@@ -35,8 +35,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
{ enabled: !!composeId },
);
const { mutateAsync, isLoading, error, isError } =
api.compose.update.useMutation();
const { mutateAsync, isLoading } = api.compose.update.useMutation();
const form = useForm<AddComposeFile>({
defaultValues: {
@@ -76,8 +75,8 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
composeId,
});
})
.catch((e) => {
toast.error("Error to update the compose config");
.catch((_e) => {
toast.error("Error updating the Compose config");
});
};
return (
@@ -98,6 +97,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
<div className="flex flex-col gap-4 w-full outline-none focus:outline-none overflow-auto">
<CodeEditor
// disabled
language="yaml"
value={field.value}
className="font-mono"
wrapperClassName="compose-file-editor"
@@ -125,7 +125,7 @@ services:
</Form>
<div className="flex justify-between flex-col lg:flex-row gap-2">
<div className="w-full flex flex-col lg:flex-row gap-4 items-end">
<RandomizeCompose composeId={composeId} />
<ShowUtilities composeId={composeId} />
</div>
<Button
type="submit"

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