From 8ae56587d5cc4805761505313b133688be467be2 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:11:54 +0200 Subject: [PATCH] feat(ui): Shadow UI redesign, domain management, and DNS autoconfigure (#11119) Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- .env.development.example | 9 +- .env.testing | 1 - .github/workflows/coolify-sha-build.yml | 32 +- .gitignore | 1 + AGENTS.md | 1 - DEVELOPMENT.md | 18 +- RELEASE.md | 3 +- UI_REDESIGN.md | 640 ++++ app/Actions/Server/CleanupDocker.php | 5 +- app/Console/Commands/Init.php | 9 +- .../Api/ApplicationsController.php | 57 +- .../Controllers/Api/ServersController.php | 2 +- app/Http/Kernel.php | 3 + .../Middleware/AddServerTimingHeaders.php | 152 + app/Jobs/ApplicationDeploymentJob.php | 4 +- app/Jobs/PullTemplatesFromCDN.php | 20 +- .../InteractsWithCloudflareDomainConnect.php | 257 ++ app/Livewire/Dashboard.php | 16 +- app/Livewire/Dashboard/ActiveDeployments.php | 68 + app/Livewire/DeploymentsIndicator.php | 32 + .../Concerns/TogglesNotificationEvents.php | 37 + app/Livewire/Notifications/Discord.php | 3 +- app/Livewire/Notifications/Email.php | 3 +- app/Livewire/Notifications/Pushover.php | 3 +- app/Livewire/Notifications/Slack.php | 3 +- app/Livewire/Notifications/Telegram.php | 3 +- app/Livewire/Notifications/Webhook.php | 3 +- .../Project/Application/Deployment/Index.php | 189 +- app/Livewire/Project/Application/Domains.php | 1557 +++++++++ app/Livewire/Project/Application/General.php | 26 +- app/Livewire/Project/Application/Heading.php | 3 + app/Livewire/Project/Application/Previews.php | 5 +- app/Livewire/Project/Database/BackupEdit.php | 12 +- app/Livewire/Project/Index.php | 67 +- app/Livewire/Project/New/Select.php | 25 +- app/Livewire/Project/Resource/Index.php | 24 +- app/Livewire/Project/Service/Domains.php | 1268 +++++++ app/Livewire/Project/Service/Index.php | 12 + .../Shared/EnvironmentVariable/All.php | 427 ++- .../Shared/EnvironmentVariable/Show.php | 112 +- .../EnvironmentVariable/ShowHardcoded.php | 2 + app/Livewire/Project/Shared/Logs.php | 31 +- app/Livewire/Project/Shared/Terminal.php | 2 + app/Livewire/Project/Shared/Webhooks.php | 2 +- app/Livewire/Project/Show.php | 66 +- app/Livewire/Server/Delete.php | 5 + app/Livewire/Server/LogDrains.php | 4 +- app/Livewire/Server/Navbar.php | 25 + app/Livewire/Settings/Advanced.php | 63 +- app/Livewire/Settings/Index.php | 4 +- app/Livewire/Settings/ScheduledJobs.php | 36 +- app/Livewire/SettingsEmail.php | 10 + app/Livewire/Source/Github/Change.php | 2 + app/Livewire/Subscription/PricingPlans.php | 5 + app/Livewire/Tags/Show.php | 36 +- app/Livewire/Team/AdminView.php | 75 +- app/Livewire/Team/Create.php | 1 + app/Livewire/Team/Index.php | 4 +- app/Models/Application.php | 98 +- app/Models/InstanceSettings.php | 3 + app/Models/ServiceApplication.php | 18 + app/Models/Team.php | 6 +- app/Providers/AppServiceProvider.php | 5 - app/Providers/TelescopeServiceProvider.php | 67 - app/Support/DnsRecordHints.php | 133 + .../DomainConnect/CloudflareDomainConnect.php | 338 ++ app/View/Components/Forms/Checkbox.php | 6 +- app/View/Components/Forms/EnvVarInput.php | 6 +- app/View/Components/Forms/Input.php | 6 +- bootstrap/helpers/domains.php | 15 + bootstrap/helpers/parsers.php | 31 +- bootstrap/helpers/shared.php | 347 +- bootstrap/helpers/subscriptions.php | 9 +- bun.lock | 1082 ++++++ composer.json | 9 - composer.lock | 232 +- config/app.php | 110 +- config/constants.php | 2 + config/debugbar.php | 326 -- config/services.php | 13 + config/telescope.php | 205 -- database/factories/TeamFactory.php | 3 +- ..._100000_create_telescope_entries_table.php | 70 - ...ain_dns_statuses_to_applications_table.php | 22 + ...statuses_to_service_applications_table.php | 22 + ...redirect_to_service_applications_table.php | 28 + ...private_key_to_instance_settings_table.php | 22 + database/schema/testing-schema.sql | 24 - other/nightly/versions.json | 7 +- phpunit.xml | 1 - public/vendor/telescope/app-dark.css | 8 - public/vendor/telescope/app.css | 7 - public/vendor/telescope/app.js | 2 - public/vendor/telescope/favicon.ico | Bin 26622 -> 0 bytes public/vendor/telescope/mix-manifest.json | 5 - resources/css/app.css | 3004 ++++++++++++++++- resources/css/utilities.css | 82 +- .../domain-connect/coolify.io.hosting.json | 20 + resources/js/app.js | 12 +- resources/js/terminal.js | 537 ++- resources/js/v5/Pages/Clusters.tsx | 12 +- .../canvas/application-inspector-sheet.tsx | 2 +- .../views/auth/confirm-password.blade.php | 70 +- .../views/auth/forgot-password.blade.php | 123 +- resources/views/auth/login.blade.php | 157 +- resources/views/auth/register.blade.php | 131 +- resources/views/auth/reset-password.blade.php | 122 +- .../application/settings-section.blade.php | 25 + .../applications/advanced.blade.php | 104 +- .../components/applications/links.blade.php | 91 +- .../views/components/auth/alert.blade.php | 26 + .../views/components/auth/shell.blade.php | 27 + .../views/components/backup-sidebar.blade.php | 64 + .../components/boarding-progress.blade.php | 69 +- .../views/components/boarding-step.blade.php | 33 +- resources/views/components/callout.blade.php | 93 +- .../components/checkpoint-item.blade.php | 51 + .../components/dashboard/navbar.blade.php | 197 ++ .../components/database-status-info.blade.php | 67 +- .../deployment/configuration-diff.blade.php | 184 +- .../domain-conflict-modal.blade.php | 36 +- resources/views/components/empty.blade.php | 78 + .../views/components/error-page.blade.php | 66 + .../views/components/forms/button.blade.php | 41 +- .../views/components/forms/checkbox.blade.php | 57 +- .../views/components/forms/datalist.blade.php | 17 +- .../components/forms/domain-chips.blade.php | 91 + .../components/forms/env-var-input.blade.php | 29 +- .../views/components/forms/input.blade.php | 42 +- .../views/components/forms/listbox.blade.php | 77 + .../components/forms/monaco-editor.blade.php | 67 +- .../forms/searchable-listbox.blade.php | 131 + .../views/components/forms/select.blade.php | 14 +- .../views/components/forms/textarea.blade.php | 21 +- resources/views/components/helper.blade.php | 31 +- .../components/loading-on-button.blade.php | 20 +- .../components/modal-confirmation.blade.php | 125 +- .../views/components/modal-input.blade.php | 31 +- resources/views/components/navbar.blade.php | 557 +-- .../notification/event-grid.blade.php | 76 + .../notification/event-multiselect.blade.php | 59 + .../components/notification/navbar.blade.php | 40 +- .../views/components/popup-small.blade.php | 55 +- .../views/components/process-dialog.blade.php | 81 + .../views/components/profile/navbar.blade.php | 24 +- .../views/components/project/navbar.blade.php | 57 + resources/views/components/reicon.blade.php | 78 + .../views/components/resource-view.blade.php | 36 +- .../components/security/navbar.blade.php | 44 +- .../components/server-timing-hud.blade.php | 876 +++++ .../server/provider-link-modal.blade.php | 96 + .../server/provider-token-picker.blade.php | 52 + .../components/server/sidebar-proxy.blade.php | 122 +- .../server/sidebar-security.blade.php | 121 +- .../server/sidebar-sentinel.blade.php | 114 +- .../views/components/server/sidebar.blade.php | 215 +- .../service-database/sidebar.blade.php | 80 +- .../components/services/advanced.blade.php | 97 +- .../views/components/services/links.blade.php | 32 +- .../components/settings/navbar.blade.php | 47 +- .../components/settings/sidebar.blade.php | 47 +- .../shared-variables/editor.blade.php | 137 + .../views/components/slide-over.blade.php | 5 +- .../views/components/status-badge.blade.php | 49 +- .../components/table-pagination.blade.php | 88 + .../views/components/team/navbar.blade.php | 58 +- resources/views/components/toast.blade.php | 594 +--- .../views/components/top-breadcrumb.blade.php | 174 + .../views/components/top-user-menu.blade.php | 84 + .../views/components/unsaved-bar.blade.php | 37 + .../components/upgrade-progress.blade.php | 172 +- resources/views/errors/400.blade.php | 36 +- resources/views/errors/401.blade.php | 30 +- resources/views/errors/402.blade.php | 27 +- resources/views/errors/403.blade.php | 30 +- resources/views/errors/404.blade.php | 31 +- resources/views/errors/419.blade.php | 48 +- resources/views/errors/429.blade.php | 32 +- resources/views/errors/500.blade.php | 42 +- resources/views/errors/503.blade.php | 32 +- resources/views/layouts/app.blade.php | 109 +- resources/views/layouts/base.blade.php | 25 +- .../views/livewire/admin/index.blade.php | 129 +- .../views/livewire/boarding/index.blade.php | 600 ++-- resources/views/livewire/dashboard.blade.php | 352 +- .../dashboard/active-deployments.blade.php | 182 + .../livewire/deployments-indicator.blade.php | 132 +- .../livewire/destination/index.blade.php | 86 +- .../livewire/destination/navbar.blade.php | 29 +- .../livewire/destination/new/docker.blade.php | 30 +- .../livewire/destination/resources.blade.php | 102 +- .../views/livewire/destination/show.blade.php | 72 +- .../livewire/force-password-reset.blade.php | 36 +- .../views/livewire/global-search.blade.php | 1222 +++---- resources/views/livewire/help.blade.php | 35 +- .../views/livewire/layout-popups.blade.php | 68 +- .../livewire/navbar-delete-team.blade.php | 16 +- .../livewire/notifications/discord.blade.php | 131 +- .../livewire/notifications/email.blade.php | 330 +- .../livewire/notifications/pushover.blade.php | 134 +- .../livewire/notifications/slack.blade.php | 121 +- .../livewire/notifications/telegram.blade.php | 218 +- .../livewire/notifications/webhook.blade.php | 131 +- .../settings-email-send-test.blade.php | 21 + .../livewire/profile/appearance.blade.php | 134 +- .../views/livewire/profile/index.blade.php | 313 +- .../livewire/project/add-empty.blade.php | 25 +- .../project/application/advanced.blade.php | 354 +- .../application/backup/create.blade.php | 26 +- .../application/backup/index.blade.php | 268 +- .../project/application/backup/show.blade.php | 40 +- .../application/configuration.blade.php | 165 +- .../application/deployment-navbar.blade.php | 23 +- .../application/deployment/index.blade.php | 407 ++- .../application/deployment/show.blade.php | 305 +- .../project/application/domains.blade.php | 354 ++ .../project/application/general.blade.php | 647 ++-- .../project/application/heading.blade.php | 741 ++-- .../application/partials/domain-row.blade.php | 113 + .../application/preview/form.blade.php | 38 +- .../application/previews-compose.blade.php | 11 +- .../project/application/previews.blade.php | 415 ++- .../project/application/rollback.blade.php | 163 +- .../project/application/source.blade.php | 252 +- .../project/application/swarm.blade.php | 51 +- .../views/livewire/project/clone-me.blade.php | 244 +- .../database/backup-edit/danger.blade.php | 27 +- .../database/backup-edit/general.blade.php | 111 +- .../database/backup-edit/retention.blade.php | 68 +- .../project/database/backup-edit/s3.blade.php | 91 +- .../database/backup-executions.blade.php | 334 +- .../database/backup/execution.blade.php | 49 +- .../project/database/backup/index.blade.php | 31 +- .../database/clickhouse/general.blade.php | 166 +- .../project/database/configuration.blade.php | 189 +- .../create-scheduled-backup.blade.php | 10 +- .../database/dragonfly/general.blade.php | 168 +- .../project/database/heading.blade.php | 456 ++- .../project/database/health.blade.php | 55 +- .../project/database/import-form.blade.php | 252 +- .../project/database/import.blade.php | 17 +- .../project/database/init-script.blade.php | 17 +- .../project/database/keydb/general.blade.php | 179 +- .../database/mariadb/general.blade.php | 223 +- .../database/mongodb/general.blade.php | 194 +- .../project/database/mysql/general.blade.php | 219 +- .../database/postgresql/general.blade.php | 254 +- .../project/database/redis/general.blade.php | 217 +- .../database/scheduled-backups.blade.php | 320 +- .../project/delete-environment.blade.php | 2 +- .../views/livewire/project/edit.blade.php | 48 +- .../project/environment-edit.blade.php | 84 +- .../views/livewire/project/index.blade.php | 395 ++- .../project/new/docker-compose.blade.php | 52 +- .../project/new/docker-image.blade.php | 46 +- ...ub-private-repository-deploy-key.blade.php | 207 +- .../new/github-private-repository.blade.php | 304 +- .../new/gitlab-private-repository.blade.php | 299 +- .../new/public-git-repository.blade.php | 202 +- .../livewire/project/new/select.blade.php | 886 +++-- .../project/new/simple-dockerfile.blade.php | 23 +- .../livewire/project/resource/index.blade.php | 916 ++--- .../project/service/configuration.blade.php | 199 +- .../service/database-backups.blade.php | 102 +- .../project/service/domains.blade.php | 292 ++ .../project/service/edit-domain.blade.php | 44 +- .../project/service/file-storage.blade.php | 31 +- .../project/service/heading.blade.php | 725 ++-- .../livewire/project/service/index.blade.php | 299 +- .../service/partials/domain-table.blade.php | 142 + .../project/service/resource-card.blade.php | 154 +- .../project/service/stack-form.blade.php | 116 +- .../project/service/storage.blade.php | 369 +- .../shared/cloudflare-autoconfigure.blade.php | 239 ++ .../shared/configuration-checker.blade.php | 60 +- .../livewire/project/shared/danger.blade.php | 71 +- .../project/shared/destination.blade.php | 322 +- .../shared/environment-variable/add.blade.php | 97 +- .../shared/environment-variable/all.blade.php | 312 +- .../show-hardcoded.blade.php | 47 +- .../environment-variable/show.blade.php | 542 ++- .../execute-container-command.blade.php | 235 +- .../project/shared/get-logs.blade.php | 439 ++- .../project/shared/health-checks.blade.php | 154 +- .../livewire/project/shared/logs.blade.php | 147 +- .../livewire/project/shared/metrics.blade.php | 630 ++-- .../shared/partials/dns-copy-cell.blade.php | 45 + .../project/shared/resource-details.blade.php | 4 +- .../project/shared/resource-limits.blade.php | 65 +- .../shared/resource-operations.blade.php | 302 +- .../shared/scheduled-task/add.blade.php | 57 +- .../shared/scheduled-task/all.blade.php | 108 +- .../scheduled-task/executions.blade.php | 149 +- .../shared/scheduled-task/show.blade.php | 79 +- .../project/shared/storages/all.blade.php | 30 +- .../project/shared/storages/show.blade.php | 32 +- .../storages/volume-backups/danger.blade.php | 45 +- .../volume-backups/executions.blade.php | 333 +- .../storages/volume-backups/general.blade.php | 64 +- .../volume-backups/retention.blade.php | 61 +- .../storages/volume-backups/s3.blade.php | 100 +- .../livewire/project/shared/tags.blade.php | 100 +- .../project/shared/terminal.blade.php | 162 +- .../project/shared/webhooks.blade.php | 180 +- .../views/livewire/project/show.blade.php | 383 ++- .../livewire/security/api-tokens.blade.php | 471 ++- .../security/cloud-init-script-form.blade.php | 19 +- .../security/cloud-init-script/show.blade.php | 39 +- .../security/cloud-init-scripts.blade.php | 87 +- .../cloud-provider-token-form.blade.php | 120 +- .../cloud-provider-token/show.blade.php | 52 +- .../security/cloud-provider-tokens.blade.php | 123 +- .../livewire/security/cloud-tokens.blade.php | 20 +- .../security/private-key/create.blade.php | 33 +- .../security/private-key/index.blade.php | 212 +- .../security/private-key/show.blade.php | 107 +- .../views/livewire/server/advanced.blade.php | 71 +- .../server/ca-certificate/show.blade.php | 149 +- .../views/livewire/server/charts.blade.php | 540 ++- .../cloud-provider-token/show.blade.php | 96 +- .../server/cloudflare-tunnel.blade.php | 207 +- .../livewire/server/create-page.blade.php | 26 +- .../views/livewire/server/create.blade.php | 169 +- .../views/livewire/server/delete.blade.php | 59 +- .../livewire/server/destinations.blade.php | 111 +- .../docker-cleanup-executions.blade.php | 188 +- .../livewire/server/docker-cleanup.blade.php | 177 +- .../views/livewire/server/index.blade.php | 201 +- .../livewire/server/log-drains.blade.php | 188 +- .../views/livewire/server/navbar.blade.php | 602 ++-- .../server/new/by-digital-ocean.blade.php | 386 +-- .../livewire/server/new/by-hetzner.blade.php | 424 +-- .../views/livewire/server/new/by-ip.blade.php | 139 +- .../livewire/server/new/by-vultr.blade.php | 361 +- .../partials/localhost-general.blade.php | 108 + .../server/private-key/show.blade.php | 160 +- .../views/livewire/server/proxy.blade.php | 340 +- .../dynamic-configuration-navbar.blade.php | 13 +- .../proxy/dynamic-configurations.blade.php | 111 +- .../livewire/server/proxy/logs.blade.php | 20 +- .../livewire/server/proxy/show.blade.php | 22 +- .../views/livewire/server/resources.blade.php | 267 +- .../server/security/patches.blade.php | 214 +- .../server/security/terminal-access.blade.php | 70 +- .../views/livewire/server/sentinel.blade.php | 190 +- .../livewire/server/sentinel/logs.blade.php | 20 +- .../livewire/server/sentinel/show.blade.php | 22 +- .../views/livewire/server/show.blade.php | 1030 ++---- .../views/livewire/server/swarm.blade.php | 65 +- .../server/validate-and-install.blade.php | 257 +- .../views/livewire/settings-backup.blade.php | 81 +- .../livewire/settings-dropdown.blade.php | 401 +-- .../views/livewire/settings-email.blade.php | 127 +- .../views/livewire/settings-oauth.blade.php | 100 +- .../livewire/settings/advanced.blade.php | 258 +- .../views/livewire/settings/index.blade.php | 165 +- .../settings/scheduled-jobs.blade.php | 605 ++-- .../views/livewire/settings/updates.blade.php | 91 +- .../environment/index.blade.php | 83 +- .../environment/show.blade.php | 36 +- .../livewire/shared-variables/index.blade.php | 93 +- .../shared-variables/project/index.blade.php | 58 +- .../shared-variables/project/show.blade.php | 36 +- .../shared-variables/server/index.blade.php | 56 +- .../shared-variables/server/show.blade.php | 40 +- .../shared-variables/team/index.blade.php | 31 +- .../livewire/source/github/change.blade.php | 873 ++--- .../livewire/source/github/create.blade.php | 88 +- .../livewire/source/gitlab/change.blade.php | 509 +-- .../livewire/source/gitlab/create.blade.php | 87 +- .../views/livewire/storage/form.blade.php | 48 +- .../views/livewire/storage/index.blade.php | 90 +- .../livewire/storage/resources.blade.php | 215 +- .../views/livewire/storage/show.blade.php | 160 +- .../livewire/subscription/actions.blade.php | 59 +- .../livewire/subscription/index.blade.php | 66 +- .../subscription/pricing-plans.blade.php | 184 +- .../livewire/subscription/show.blade.php | 8 +- .../views/livewire/switch-team.blade.php | 66 +- .../views/livewire/tags/deployments.blade.php | 75 +- resources/views/livewire/tags/show.blade.php | 529 ++- .../views/livewire/team/admin-view.blade.php | 124 +- .../views/livewire/team/create.blade.php | 11 +- resources/views/livewire/team/index.blade.php | 215 +- .../views/livewire/team/invitations.blade.php | 118 +- .../views/livewire/team/invite-link.blade.php | 46 +- .../views/livewire/team/member.blade.php | 124 +- .../livewire/team/member/index.blade.php | 217 +- .../views/livewire/terminal/index.blade.php | 263 +- resources/views/livewire/upgrade.blade.php | 230 +- resources/views/source/all.blade.php | 162 +- routes/web.php | 26 + storage/debugbar/.gitignore | 2 - templates/service-templates-latest.json | 4 +- tests/Feature/AddServerTimingHeadersTest.php | 199 ++ .../AdvancedMenuIconConsistencyTest.php | 52 + tests/Feature/ApiTokensRevokeButtonTest.php | 9 + tests/Feature/ApplicationClearDomainsTest.php | 104 + tests/Feature/ApplicationDomainsTest.php | 1167 +++++++ .../ApplicationGitSourceButtonLayoutTest.php | 91 + .../ApplicationHttpBasicAuthUiTest.php | 114 + tests/Feature/ApplicationRedirectTest.php | 87 +- tests/Feature/ApplicationRollbackIconTest.php | 16 + .../Auth/TwoFactorChallengeAccessTest.php | 5 +- .../EnvironmentVariableValueHidingTest.php | 6 +- .../NavbarDeleteTeamAuthorizationTest.php | 15 +- .../Authorization/TeamAuthorizationTest.php | 20 +- tests/Feature/BackupEditValidationTest.php | 72 +- tests/Feature/BackupSearchTest.php | 28 +- .../BrowserTerminalIconConsistencyTest.php | 36 + tests/Feature/CloudflareDomainConnectTest.php | 127 + tests/Feature/DashboardDeploymentsTest.php | 177 + tests/Feature/DashboardNavbarLayoutTest.php | 20 + .../DashboardServersEmptyStateTest.php | 107 + .../Feature/DatabaseSslStatusRefreshTest.php | 28 + tests/Feature/DeploymentLogsLayoutTest.php | 135 +- ...ymentsIndicatorDashboardVisibilityTest.php | 88 + .../DeploymentsIndicatorLayoutTest.php | 23 + tests/Feature/DnsRecordHintsTest.php | 45 + tests/Feature/DomainChipsComponentTest.php | 66 + tests/Feature/EmptyStateComponentTest.php | 59 + tests/Feature/EnvironmentEditUiTest.php | 23 + .../ConvertContainerEnvsToArrayTest.php | 2 - .../EnvironmentVariableAsyncLoadTest.php | 123 + ...ronmentVariableMultilineToggleViewTest.php | 40 +- .../EnvironmentVariablePaginationTest.php | 244 ++ .../Feature/EnvironmentVariableSearchTest.php | 69 +- tests/Feature/ErrorPagesRedesignTest.php | 71 + tests/Feature/FormsButtonLoadingTest.php | 69 + tests/Feature/GithubAppSettingsLayoutTest.php | 85 + .../Feature/HeaderVersionReleaseLinkTest.php | 17 + tests/Feature/HelperInfoButtonTest.php | 63 + tests/Feature/HiddenModalShellLayoutTest.php | 45 + .../HideNativeSearchClearButtonTest.php | 16 + tests/Feature/ImportBackupUploadIconTest.php | 38 + .../Jobs/CheckTraefikVersionJobTest.php | 15 + .../Feature/ListboxTriggerTruncationTest.php | 65 + .../ConfigurationChangeRefreshEventsTest.php | 18 + tests/Feature/LogFontStylingTest.php | 13 +- tests/Feature/LoginPageBrandingTest.php | 34 + tests/Feature/MobileResourceMenuTest.php | 18 +- ...ConfirmationDestructiveActionsIconTest.php | 21 + .../ModalConfirmationStep1NoCancelTest.php | 15 + ...bleLivewireComponentsAuthorizationTest.php | 10 + tests/Feature/PageTitleXlHiddenTest.php | 72 + .../PasswordVisibilityComponentTest.php | 43 +- .../PrivateKeyIndexNavbarActionsTest.php | 69 + tests/Feature/ProcessDialogTest.php | 41 + .../ProfileAppearanceNavigationTest.php | 33 +- ...rojectAndEnvironmentSettingsButtonTest.php | 75 + tests/Feature/ProjectIndexEmptyStateTest.php | 64 +- .../PullServiceTemplatesFromCdnTest.php | 136 + .../Feature/RealtimeTerminalPackagingTest.php | 115 +- .../ResourceHeadingUnifiedNavbarTest.php | 77 + tests/Feature/ResourceLimitsIconTest.php | 21 + tests/Feature/ResourcePageTitleTest.php | 158 + .../Feature/ResponsiveCheckboxLayoutTest.php | 14 +- .../SearchableListboxComponentTest.php | 54 + .../Security/CommandInjectionSecurityTest.php | 3 +- tests/Feature/SentinelUnsavedBarFlashTest.php | 110 + .../ServerDangerMenuVisibilityTest.php | 78 + tests/Feature/ServerDestinationsPageTest.php | 7 +- .../ServerNavbarStatusVisibilityTest.php | 28 + .../ServerResourcesTableMobileLayoutTest.php | 57 + .../ServerSecuritySidebarIconsTest.php | 27 + tests/Feature/ServiceDomainsTest.php | 389 +++ .../ServiceTemplatesLastUpdatedHintTest.php | 35 +- tests/Feature/SettingsAccessListboxTest.php | 125 + .../Feature/SettingsBackupEmptyStateTest.php | 86 + tests/Feature/SettingsDropdownTest.php | 15 +- .../SettingsEmailSendTestPlacementTest.php | 72 + ...tingsScheduledJobsRefreshPlacementTest.php | 20 + tests/Feature/SettingsSidebarStickyTest.php | 36 + .../SettingsTitleSidebarAlignmentTest.php | 43 + tests/Feature/SettingsUpdatesIconTest.php | 17 + .../SharedVariablesTitleOnDesktopTest.php | 55 + tests/Feature/SidebarNavigationMarkupTest.php | 10 +- .../SidebarNavigationPreferencesTest.php | 6 + tests/Feature/StorageSettingsLayoutTest.php | 74 + .../SubscriptionPricingPageTest.php | 105 + .../UnsubscribedProfileAccessTest.php | 80 + tests/Feature/TablePaginationLoadingTest.php | 88 + tests/Feature/TagsShowPageTest.php | 117 + tests/Feature/TeamCreateButtonAccentTest.php | 18 + .../Feature/TeamCreateSetsMcpDefaultTest.php | 37 + tests/Feature/TeamInvitationUiTest.php | 75 + tests/Feature/TeamScopedDestinationTest.php | 4 +- tests/Feature/TerminalPageHeaderTest.php | 115 + tests/Feature/TopUserMenuTest.php | 20 + tests/Feature/VolumeBackupTest.php | 9 +- tests/Unit/CloudProviderTokenFormViewTest.php | 13 +- tests/Unit/CountDomainsHelperTest.php | 22 + tests/Unit/DnsRecordTypeHelpersTest.php | 48 + tests/Unit/ResolveServerIpAddressTest.php | 95 + tests/Unit/SharedLogsContainerLookupTest.php | 33 + tests/Unit/SslipDomainWarningTest.php | 28 + versions.json | 7 +- 497 files changed, 44045 insertions(+), 22630 deletions(-) create mode 100644 UI_REDESIGN.md create mode 100644 app/Http/Middleware/AddServerTimingHeaders.php create mode 100644 app/Livewire/Concerns/InteractsWithCloudflareDomainConnect.php create mode 100644 app/Livewire/Dashboard/ActiveDeployments.php create mode 100644 app/Livewire/Notifications/Concerns/TogglesNotificationEvents.php create mode 100644 app/Livewire/Project/Application/Domains.php create mode 100644 app/Livewire/Project/Service/Domains.php delete mode 100644 app/Providers/TelescopeServiceProvider.php create mode 100644 app/Support/DnsRecordHints.php create mode 100644 app/Support/DomainConnect/CloudflareDomainConnect.php create mode 100644 bun.lock delete mode 100644 config/debugbar.php delete mode 100644 config/telescope.php delete mode 100644 database/migrations/2018_08_08_100000_create_telescope_entries_table.php create mode 100644 database/migrations/2026_07_24_091627_add_domain_dns_statuses_to_applications_table.php create mode 100644 database/migrations/2026_07_24_110621_add_domain_dns_statuses_to_service_applications_table.php create mode 100644 database/migrations/2026_07_29_100747_add_redirect_to_service_applications_table.php create mode 100644 database/migrations/2026_08_03_192434_add_domain_connect_private_key_to_instance_settings_table.php delete mode 100644 public/vendor/telescope/app-dark.css delete mode 100644 public/vendor/telescope/app.css delete mode 100644 public/vendor/telescope/app.js delete mode 100644 public/vendor/telescope/favicon.ico delete mode 100644 public/vendor/telescope/mix-manifest.json create mode 100644 resources/domain-connect/coolify.io.hosting.json create mode 100644 resources/views/components/application/settings-section.blade.php create mode 100644 resources/views/components/auth/alert.blade.php create mode 100644 resources/views/components/auth/shell.blade.php create mode 100644 resources/views/components/backup-sidebar.blade.php create mode 100644 resources/views/components/checkpoint-item.blade.php create mode 100644 resources/views/components/dashboard/navbar.blade.php create mode 100644 resources/views/components/empty.blade.php create mode 100644 resources/views/components/error-page.blade.php create mode 100644 resources/views/components/forms/domain-chips.blade.php create mode 100644 resources/views/components/forms/listbox.blade.php create mode 100644 resources/views/components/forms/searchable-listbox.blade.php create mode 100644 resources/views/components/notification/event-grid.blade.php create mode 100644 resources/views/components/notification/event-multiselect.blade.php create mode 100644 resources/views/components/process-dialog.blade.php create mode 100644 resources/views/components/project/navbar.blade.php create mode 100644 resources/views/components/reicon.blade.php create mode 100644 resources/views/components/server-timing-hud.blade.php create mode 100644 resources/views/components/server/provider-link-modal.blade.php create mode 100644 resources/views/components/server/provider-token-picker.blade.php create mode 100644 resources/views/components/shared-variables/editor.blade.php create mode 100644 resources/views/components/table-pagination.blade.php create mode 100644 resources/views/components/top-breadcrumb.blade.php create mode 100644 resources/views/components/top-user-menu.blade.php create mode 100644 resources/views/components/unsaved-bar.blade.php create mode 100644 resources/views/livewire/dashboard/active-deployments.blade.php create mode 100644 resources/views/livewire/partials/settings-email-send-test.blade.php create mode 100644 resources/views/livewire/project/application/domains.blade.php create mode 100644 resources/views/livewire/project/application/partials/domain-row.blade.php create mode 100644 resources/views/livewire/project/service/domains.blade.php create mode 100644 resources/views/livewire/project/service/partials/domain-table.blade.php create mode 100644 resources/views/livewire/project/shared/cloudflare-autoconfigure.blade.php create mode 100644 resources/views/livewire/project/shared/partials/dns-copy-cell.blade.php create mode 100644 resources/views/livewire/server/partials/localhost-general.blade.php delete mode 100755 storage/debugbar/.gitignore create mode 100644 tests/Feature/AddServerTimingHeadersTest.php create mode 100644 tests/Feature/AdvancedMenuIconConsistencyTest.php create mode 100644 tests/Feature/ApiTokensRevokeButtonTest.php create mode 100644 tests/Feature/ApplicationClearDomainsTest.php create mode 100644 tests/Feature/ApplicationDomainsTest.php create mode 100644 tests/Feature/ApplicationGitSourceButtonLayoutTest.php create mode 100644 tests/Feature/ApplicationHttpBasicAuthUiTest.php create mode 100644 tests/Feature/ApplicationRollbackIconTest.php create mode 100644 tests/Feature/BrowserTerminalIconConsistencyTest.php create mode 100644 tests/Feature/CloudflareDomainConnectTest.php create mode 100644 tests/Feature/DashboardDeploymentsTest.php create mode 100644 tests/Feature/DashboardNavbarLayoutTest.php create mode 100644 tests/Feature/DashboardServersEmptyStateTest.php create mode 100644 tests/Feature/DeploymentsIndicatorDashboardVisibilityTest.php create mode 100644 tests/Feature/DnsRecordHintsTest.php create mode 100644 tests/Feature/DomainChipsComponentTest.php create mode 100644 tests/Feature/EmptyStateComponentTest.php create mode 100644 tests/Feature/EnvironmentEditUiTest.php create mode 100644 tests/Feature/EnvironmentVariableAsyncLoadTest.php create mode 100644 tests/Feature/EnvironmentVariablePaginationTest.php create mode 100644 tests/Feature/ErrorPagesRedesignTest.php create mode 100644 tests/Feature/FormsButtonLoadingTest.php create mode 100644 tests/Feature/GithubAppSettingsLayoutTest.php create mode 100644 tests/Feature/HeaderVersionReleaseLinkTest.php create mode 100644 tests/Feature/HelperInfoButtonTest.php create mode 100644 tests/Feature/HiddenModalShellLayoutTest.php create mode 100644 tests/Feature/HideNativeSearchClearButtonTest.php create mode 100644 tests/Feature/ImportBackupUploadIconTest.php create mode 100644 tests/Feature/ListboxTriggerTruncationTest.php create mode 100644 tests/Feature/LoginPageBrandingTest.php create mode 100644 tests/Feature/ModalConfirmationDestructiveActionsIconTest.php create mode 100644 tests/Feature/ModalConfirmationStep1NoCancelTest.php create mode 100644 tests/Feature/PageTitleXlHiddenTest.php create mode 100644 tests/Feature/PrivateKeyIndexNavbarActionsTest.php create mode 100644 tests/Feature/ProcessDialogTest.php create mode 100644 tests/Feature/ProjectAndEnvironmentSettingsButtonTest.php create mode 100644 tests/Feature/PullServiceTemplatesFromCdnTest.php create mode 100644 tests/Feature/ResourceHeadingUnifiedNavbarTest.php create mode 100644 tests/Feature/ResourceLimitsIconTest.php create mode 100644 tests/Feature/ResourcePageTitleTest.php create mode 100644 tests/Feature/SearchableListboxComponentTest.php create mode 100644 tests/Feature/SentinelUnsavedBarFlashTest.php create mode 100644 tests/Feature/ServerDangerMenuVisibilityTest.php create mode 100644 tests/Feature/ServerResourcesTableMobileLayoutTest.php create mode 100644 tests/Feature/ServerSecuritySidebarIconsTest.php create mode 100644 tests/Feature/ServiceDomainsTest.php create mode 100644 tests/Feature/SettingsAccessListboxTest.php create mode 100644 tests/Feature/SettingsBackupEmptyStateTest.php create mode 100644 tests/Feature/SettingsEmailSendTestPlacementTest.php create mode 100644 tests/Feature/SettingsScheduledJobsRefreshPlacementTest.php create mode 100644 tests/Feature/SettingsSidebarStickyTest.php create mode 100644 tests/Feature/SettingsTitleSidebarAlignmentTest.php create mode 100644 tests/Feature/SettingsUpdatesIconTest.php create mode 100644 tests/Feature/SharedVariablesTitleOnDesktopTest.php create mode 100644 tests/Feature/StorageSettingsLayoutTest.php create mode 100644 tests/Feature/Subscription/SubscriptionPricingPageTest.php create mode 100644 tests/Feature/Subscription/UnsubscribedProfileAccessTest.php create mode 100644 tests/Feature/TablePaginationLoadingTest.php create mode 100644 tests/Feature/TagsShowPageTest.php create mode 100644 tests/Feature/TeamCreateButtonAccentTest.php create mode 100644 tests/Feature/TeamCreateSetsMcpDefaultTest.php create mode 100644 tests/Feature/TeamInvitationUiTest.php create mode 100644 tests/Feature/TerminalPageHeaderTest.php create mode 100644 tests/Feature/TopUserMenuTest.php create mode 100644 tests/Unit/CountDomainsHelperTest.php create mode 100644 tests/Unit/DnsRecordTypeHelpersTest.php create mode 100644 tests/Unit/ResolveServerIpAddressTest.php create mode 100644 tests/Unit/SharedLogsContainerLookupTest.php create mode 100644 tests/Unit/SslipDomainWarningTest.php diff --git a/.env.development.example b/.env.development.example index 0454dd44a..29162a59a 100644 --- a/.env.development.example +++ b/.env.development.example @@ -29,11 +29,12 @@ DB_PORT=5432 # DB_WRITE_PASSWORD= # DB_STICKY=true -# Enable Laravel Telescope for debugging -TELESCOPE_ENABLED=false -# Enable Laravel Debugbar (disabled by default; set true when needed) -DEBUGBAR_ENABLED=false + +# Server-Timing headers + on-screen HUD (defaults ON when APP_ENV=local). +# Force on in any environment (including production): SERVER_TIMING_ENABLED=true +# Force off even in local: SERVER_TIMING_ENABLED=false +# SERVER_TIMING_ENABLED=true # Vite dev server. Defaults to localhost. For phone/LAN/Tailscale access, set to # the host machine's reachable IP (e.g. VITE_HOST=100.75.155.70), then recreate vite. diff --git a/.env.testing b/.env.testing index d72330b5d..48d893596 100644 --- a/.env.testing +++ b/.env.testing @@ -9,7 +9,6 @@ CACHE_DRIVER=array SESSION_DRIVER=array QUEUE_CONNECTION=sync MAIL_MAILER=array -TELESCOPE_ENABLED=false REDIS_HOST=127.0.0.1 diff --git a/.github/workflows/coolify-sha-build.yml b/.github/workflows/coolify-sha-build.yml index 0a5315a49..8c653e1d1 100644 --- a/.github/workflows/coolify-sha-build.yml +++ b/.github/workflows/coolify-sha-build.yml @@ -76,15 +76,35 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }} + env: + REGISTRY: ${{ env.GITHUB_REGISTRY }} + BRANCH: ${{ github.ref_name }} + SHA: ${{ github.sha }} run: | + IMAGE="${REGISTRY}/${IMAGE_NAME}" + TAG_ARGS=(--tag "${IMAGE}:sha-${SHA}") + # Moving tag for the latest production-line SHA image (v4.x only). + if [ "${BRANCH}" = "v4.x" ]; then + TAG_ARGS+=(--tag "${IMAGE}:edge") + fi docker buildx imagetools create \ - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-amd64 \ - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-aarch64 \ - --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }} + "${IMAGE}:sha-${SHA}-amd64" \ + "${IMAGE}:sha-${SHA}-aarch64" \ + "${TAG_ARGS[@]}" - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }} + env: + REGISTRY: ${{ env.DOCKER_REGISTRY }} + BRANCH: ${{ github.ref_name }} + SHA: ${{ github.sha }} run: | + IMAGE="${REGISTRY}/${IMAGE_NAME}" + TAG_ARGS=(--tag "${IMAGE}:sha-${SHA}") + # Moving tag for the latest production-line SHA image (v4.x only). + if [ "${BRANCH}" = "v4.x" ]; then + TAG_ARGS+=(--tag "${IMAGE}:edge") + fi docker buildx imagetools create \ - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-amd64 \ - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-aarch64 \ - --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }} + "${IMAGE}:sha-${SHA}-amd64" \ + "${IMAGE}:sha-${SHA}-aarch64" \ + "${TAG_ARGS[@]}" diff --git a/.gitignore b/.gitignore index 401adc775..086b946d0 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ CHANGELOG.md /.workspaces tests/Browser/Screenshots tests/v4/Browser/Screenshots +ref # Local generated Lima configs .dev/bin/ diff --git a/AGENTS.md b/AGENTS.md index b4faa103a..566d79777 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -167,7 +167,6 @@ This application is a Laravel application and its main Laravel ecosystems packag - laravel/boost (BOOST) - v2 - laravel/dusk (DUSK) - v8 - laravel/pint (PINT) - v1 -- laravel/telescope (TELESCOPE) - v5 - pestphp/pest (PEST) - v4 - phpunit/phpunit (PHPUNIT) - v12 - rector/rector (RECTOR) - v2 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 33d438b91..910657159 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -140,13 +140,19 @@ After installing Docker (or Orbstack) and Spin, verify the installation: |------|-----|------| | Laravel Horizon (scheduler) | `http://localhost:8000/horizon` | Only accessible when logged in as root user | | Mailpit (email catcher) | `http://localhost:8025` | | - | Telescope (debugging tool) | `http://localhost:8000/telescope` | Disabled by default | -> [!NOTE] -> To enable Telescope, add the following to your `.env` file: -> ```env -> TELESCOPE_ENABLED=true -> ``` + **Server-Timing + HUD** (headers + bottom-right pill on full HTML pages): + + | Setting | Effect | + |---------|--------| + | `APP_ENV=local` and `SERVER_TIMING_ENABLED` unset | **On** (default in dev) | + | `SERVER_TIMING_ENABLED=true` | **On** in any env, including production | + | `SERVER_TIMING_ENABLED=false` | **Off** even when `APP_ENV=local` | + + Metrics: `app` / `db` / `php` / `dbslow` (ms), `queries`, `html` (bytes), `mem` (MB). + HUD keeps a request log (click row → AI-ready dump). Production: enable only + temporarily (`SERVER_TIMING_ENABLED=true`); if you use `config:cache`, rebuild + or clear config after changing the env var. ## Development Notes diff --git a/RELEASE.md b/RELEASE.md index 0f956ebfd..e83609ad3 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -20,7 +20,7 @@ Coolify uses two long-lived branches so production fixes can ship without waitin | Branch | Role | Docker image tags | How it ships | | --- | --- | --- | --- | -| **`v4.x`** | Production / releasable line | `sha-` via **Build Coolify (SHA)** | GitHub release promotes the SHA image to a semantic version (and `latest` for stable releases) | +| **`v4.x`** | Production / releasable line | `sha-` and moving `edge` via **Build Coolify (SHA)** | GitHub release promotes the SHA image to a semantic version (and `latest` for stable releases) | | **`next`** | Development line for features and larger changes | Branch tag (for example `next`) via **Staging Build** | Becomes production only after merge into `v4.x` | ### Where to merge @@ -59,6 +59,7 @@ Only commits on **`v4.x`** produce production SHA images and can be tagged for a - Merge the release commit into `v4.x` through a pull request. - The `Build Coolify (SHA)` workflow builds AMD64 and ARM64 images and publishes them to Docker Hub and GHCR using immutable architecture tags. - After both builds complete, the workflow creates the multi-architecture `sha-` manifest in both registries. + - For pushes to **`v4.x`**, the same multi-architecture manifest is also tagged as `edge`, so `coollabsio/coolify:edge` always points at the latest production-line SHA image. Builds from `main` publish only the immutable `sha-` tags. - This workflow does not update a semantic version tag or `latest`. 3. **Wait for the SHA Image** diff --git a/UI_REDESIGN.md b/UI_REDESIGN.md new file mode 100644 index 000000000..194d7f0c3 --- /dev/null +++ b/UI_REDESIGN.md @@ -0,0 +1,640 @@ +# Coolify UI redesign + +This branch restyles Coolify without changing its Livewire + Blade + Alpine + +Tailwind v4 architecture. The visual system now covers the global shell, +project and environment pages, application navigation, settings surfaces, +tables, modals, toasts, terminals, and metrics. + +Use this file as the source of truth when updating another page. The older +Graphite-only notes are no longer accurate. + +Onboarding validation and live server validation checkpoints share +`` (idle / pending / running / success / error) inside a +compact divided list, not legacy green check SVGs or fixed-width status rows. + +> **Maintainer rules** +> +> - Keep the work frontend-focused unless existing data must be exposed to the +> view. +> - Preserve routes, Livewire bindings, permissions, confirmations, and working +> interactions while changing layout and presentation. +> - Do not write or run tests for this redesign branch. +> - Validate Blade with `docker exec coolify php artisan view:cache`, then clear +> it with `docker exec coolify php artisan view:clear`. +> - Build frontend assets in the Vitee container with +> `docker exec coolify-vite npm run build`. +> - Use existing components before adding another styling abstraction. + +--- + +## 1. Visual direction + +The interface is compact and product-focused: + +- near-neutral layered surfaces instead of large bordered boxes; +- 13–14px UI typography and 32px controls; +- hairline rings instead of heavy borders; +- full-width data tables for dense collections; +- outline Reicon glyphs through ``; +- the Coolify purple brand accent in light mode; +- the readable Coolify yellow accent in dark mode; +- subtle active-item gradients that fade completely into the surrounding + background at the far edge, with the active pill rounded only on the left + (square on the right so it bleeds into the content edge); +- sentence-case labels and headings; +- never use the em dash (`—`) in UI copy. Prefer a period, colon, comma, or + ASCII hyphen (`-`) for empty cells and separators. + +Avoid oversized titles, generic dashboard cards, strong shadows, thick +dividers, native browser selects, and isolated colored buttons that do not +match the current action styles. + +--- + +## 2. Development and cascade notes + +PHP runs in the `coolify` container. The development app is normally available +at `http://localhost:8000`, with Vite on port `5173`. + +`resources/css/app.css` still contains unlayered global element rules for +headings, labels, and tables. Tailwind utilities are layered, so the +unlayered rules can win unexpectedly. + +The settings and dense-surface CSS therefore lives as plain unlayered CSS near +the end of `resources/css/app.css`, beginning at: + +```css +/* Coollabs layer-card settings surfaces */ +``` + +Important consequences: + +- scope restyled forms with `.application-settings-form` or + `.application-settings-workspace`; +- add shared surface overrides to the unlayered block instead of stacking + `!important` utilities; +- listbox panels require ancestors with `overflow: visible`; +- anchored cards use `scroll-margin-top: 7rem` to clear both fixed navigation + layers; +- modal shells reuse the layer-card classes but keep content-width sizing on + desktop; +- Alpine code inside quoted Blade attributes must not introduce conflicting + quote characters. + +--- + +## 3. Tokens and color behavior + +The surface ladder is defined in `resources/css/app.css`. + +| Token | Light | Dark | Use | +|---|---|---|---| +| `--coollabs-canvas` | near white | 10% neutral | page canvas | +| `--coollabs-elevated` | 98% neutral | 15% neutral | shells and card headers | +| `--coollabs-base` | white | 17% neutral | nested card bodies | +| `--coollabs-recessed` | 96% neutral | 20% neutral | inputs and listboxes | +| `--coollabs-fill` | 92.2% neutral | 26.9% neutral | dividers and passive fills | +| `--coollabs-line` | translucent dark | 32% neutral | control borders | +| `--coollabs-hairline` | 93.5% neutral | 26.9% neutral | shell rings | +| `--coollabs-subtle` | 55.6% neutral | 70.8% neutral | labels and muted titles | + +Accent behavior is intentionally theme-aware: + +- **Light mode:** Coolify purple (`coollabs`) for active controls, focus, + primary actions, and navigation accents. +- **Dark mode:** Coolify yellow (`warning`) for the same states because the + original purple did not provide sufficient text and ring contrast. + +Do not hard-code blue focus rings or leave yellow accent utilities active in +light mode. Primary action patterns should normally follow: + +```html +bg-coollabs/10 text-coollabs ring-coollabs/25 +dark:bg-warning/15 dark:text-warning dark:ring-warning/25 +``` + +The filled top-level action/tab treatment uses the same palette at a restrained +opacity rather than a fully saturated fill. + +--- + +## 4. Page shells and navigation + +### Global shell + +- Main sidebar groups are compact, use outline Reicons, and keep a 32px row + height. +- Active sidebar rows have a curved accent rail and a subtle horizontal + gradient. The gradient must fade to the exact sidebar background at the + right edge in both themes. +- Nested items use a thin guide line with a visible active segment, not a thick + box border. +- The update badge sits on the version row and uses a tiny fully rounded + primary-action pill. + +### Layer-2 navigation + +Application and server pages use the same fixed second navigation layer +directly below the global topbar. Do not keep a large in-flow resource heading +or legacy `.navbar-main` tabs on one resource type while using the compact +layer-2 bar on another. Active tabs are a light brand fill: + +- purple tint in light mode; +- yellow tint in dark mode; +- no fully saturated tab background. + +Keep route-derived active state in Blade/Livewire. Do not rely only on Alpine +state because it can disappear after polling or a Livewire morph. + +The global topbar owns the current resource identity and its compact status +badges. Layer 2 owns route tabs, resource links, and contextual action buttons +only. If a resource is missing from `x-top-breadcrumb`, extend the global +topbar instead of repeating its name or status summary in layer 2. Mobile +resource navigation may repeat this context because the desktop global topbar +is hidden there. + +Only add layer-2 tabs when they represent real sibling routes inside one +context. Never repeat main-sidebar destinations such as Dashboard, Projects, +Terminal, Servers, Sources, Destinations, or Storage as a second tab row. A +single collection page does not need a tab just to fill the bar; keep its +primary action in the page header instead. When tabs are useful, their left edge +uses the same compact `pl-2` alignment as application navigation rather than +the content container's wide horizontal padding. + +The dashboard is a compact overview, not a metrics wall. Use two full-width +sections that follow the projects-page grid pattern: projects first, then +servers. Keep one `New` action in the page header and let its modal choose the +resource type. Place active deployments above the resource grids as a compact, +live-updating table rather than a metric card. Communicate server health with +the shared status badge. + +### Top-level dashboard destinations + +Every page opened directly from the main sidebar uses the same compact content +shell: + +- 24px page title and a 13px muted summary; +- the primary action at the top right using the restrained brand fill; +- no legacy `coolbox`, `.navbar-main`, or oversized subtitle block; +- four-column compact cards for small browsable collections; +- a dense table instead of cards when the collection is expected to grow; +- `x-empty` anatomy for empty states; +- `x-status-badge` for state and `x-reicon` for all interface icons. + +Collection cards are `min-h-28` or `min-h-32`, use a 32px icon tile, and keep +secondary metadata at 11px. They must not grow into dashboard-sized summary +cards. Sources, destinations, S3 storage, private keys, and shared-variable +scopes use this pattern. + +Top-level settings families such as Team, Notifications, Keys & Tokens, and +instance Settings use a compact header followed by a small route-derived tab +strip. The active tab uses the same purple-light/yellow-dark tint as resource +tabs. Do not nest ` + + +
+

+ Link this server to its {{ $providerLabel }} resource for provider status and power controls. +

+ + + +
+ + +
+ +
+ + or + +
+ + + + @if ($searchError) + {{ $searchError }} + @elseif ($noMatch) + + Try another token, confirm the resource ID, or verify the server IP. + + @elseif ($matched) +
+
+
+

+ {{ $matched['name'] ?? 'Matched resource' }} +

+

+ {{ $matched['id'] ?? 'Unknown ID' }} + @if ($matched['status'] ?? null) + · + {{ ucfirst($matched['status']) }} + @endif +

+
+ +
+ +
+ @endif +
+ diff --git a/resources/views/components/server/provider-token-picker.blade.php b/resources/views/components/server/provider-token-picker.blade.php new file mode 100644 index 000000000..3cf124550 --- /dev/null +++ b/resources/views/components/server/provider-token-picker.blade.php @@ -0,0 +1,52 @@ +@props([ + 'provider', + 'providerLabel', + 'routeType' => null, + 'tokens', +]) + + + @if ($tokens->isEmpty()) + + + + + + + + + + + @else + + @endif + diff --git a/resources/views/components/server/sidebar-proxy.blade.php b/resources/views/components/server/sidebar-proxy.blade.php index dba5d7db5..9461753d6 100644 --- a/resources/views/components/server/sidebar-proxy.blade.php +++ b/resources/views/components/server/sidebar-proxy.blade.php @@ -1,139 +1,49 @@ @php - $serverPageItems = [ - [ - 'label' => 'Configuration', - 'route' => 'server.show', - 'active' => request()->routeIs('server.show', 'server.advanced', 'server.private-key', 'server.cloud-provider-token', 'server.ca-certificate', 'server.cloudflare-tunnel', 'server.docker-cleanup', 'server.destinations', 'server.log-drains', 'server.metrics', 'server.swarm', 'server.delete'), - ], - [ - 'label' => 'Proxy', - 'route' => 'server.proxy', - 'active' => request()->routeIs('server.proxy', 'server.proxy.*'), - 'visible' => ! $server->isSwarmWorker() && ! $server->settings->is_build_server, - ], - [ - 'label' => 'Sentinel', - 'route' => 'server.sentinel', - 'active' => request()->routeIs('server.sentinel', 'server.sentinel.*'), - 'visible' => $server->isFunctional() && ! $server->isSwarm() && ! $server->settings->is_build_server && auth()->user()?->can('viewSentinel', $server), - ], - [ - 'label' => 'Resources', - 'route' => 'server.resources', - 'active' => request()->routeIs('server.resources'), - ], - [ - 'label' => 'Terminal', - 'route' => 'server.command', - 'active' => request()->routeIs('server.command'), - 'navigate' => false, - 'visible' => auth()->user()?->can('canAccessTerminal'), - ], - [ - 'label' => 'Security', - 'route' => 'server.security.patches', - 'active' => request()->routeIs('server.security.patches'), - 'visible' => auth()->user()?->can('update', $server), - ], - ]; $proxyMenuItems = [ [ 'label' => 'Configuration', 'route' => 'server.proxy', 'active' => request()->routeIs('server.proxy'), + 'icon' => 'settings', ], [ 'label' => 'Dynamic Configurations', 'route' => 'server.proxy.dynamic-confs', 'active' => request()->routeIs('server.proxy.dynamic-confs'), + 'icon' => 'sliders', 'visible' => $server->proxySet(), ], [ 'label' => 'Logs', 'route' => 'server.proxy.logs', 'active' => request()->routeIs('server.proxy.logs'), + 'icon' => 'file-content', 'visible' => $server->proxySet(), 'navigate' => false, ], ]; - $serverPageItems = array_values(array_filter( - $serverPageItems, - fn (array $item): bool => $item['visible'] ?? true, - )); $proxyMenuItems = array_values(array_filter( $proxyMenuItems, fn (array $item): bool => $item['visible'] ?? true, )); - $activeProxyMenuItem = collect($proxyMenuItems)->firstWhere('active', true) ?? $proxyMenuItems[0]; - $activeProxyMenuValue = (($activeProxyMenuItem['navigate'] ?? true) ? 'navigate' : 'location').'|proxy|'.route($activeProxyMenuItem['route'], $parameters); @endphp -
-
- - -
- - -
+ + diff --git a/resources/views/components/server/sidebar-security.blade.php b/resources/views/components/server/sidebar-security.blade.php index 1c30b0ad3..8238b5495 100644 --- a/resources/views/components/server/sidebar-security.blade.php +++ b/resources/views/components/server/sidebar-security.blade.php @@ -1,127 +1,36 @@ @php - $serverPageItems = [ - [ - 'label' => 'Configuration', - 'route' => 'server.show', - 'active' => request()->routeIs('server.show', 'server.advanced', 'server.private-key', 'server.cloud-provider-token', 'server.ca-certificate', 'server.cloudflare-tunnel', 'server.docker-cleanup', 'server.destinations', 'server.log-drains', 'server.metrics', 'server.swarm', 'server.delete'), - ], - [ - 'label' => 'Proxy', - 'route' => 'server.proxy', - 'active' => request()->routeIs('server.proxy', 'server.proxy.*'), - 'visible' => ! $server->isSwarmWorker() && ! $server->settings->is_build_server, - ], - [ - 'label' => 'Sentinel', - 'route' => 'server.sentinel', - 'active' => request()->routeIs('server.sentinel', 'server.sentinel.*'), - 'visible' => $server->isFunctional() && ! $server->isSwarm() && ! $server->settings->is_build_server && auth()->user()?->can('viewSentinel', $server), - ], - [ - 'label' => 'Resources', - 'route' => 'server.resources', - 'active' => request()->routeIs('server.resources'), - ], - [ - 'label' => 'Terminal', - 'route' => 'server.command', - 'active' => request()->routeIs('server.command'), - 'navigate' => false, - 'visible' => auth()->user()?->can('canAccessTerminal'), - ], - [ - 'label' => 'Security', - 'route' => 'server.security.patches', - 'active' => request()->routeIs('server.security.patches'), - 'visible' => auth()->user()?->can('update', $server), - ], - ]; $securityMenuItems = [ [ 'label' => 'Server Patching', 'route' => 'server.security.patches', 'active' => request()->routeIs('server.security.patches'), + 'icon' => 'bandage', ], [ 'label' => 'Terminal Access', 'route' => 'server.security.terminal-access', 'active' => request()->routeIs('server.security.terminal-access'), + 'icon' => 'browser-terminal', 'navigate' => false, ], ]; - $serverPageItems = array_values(array_filter( - $serverPageItems, - fn (array $item): bool => $item['visible'] ?? true, - )); - $activeSecurityMenuItem = collect($securityMenuItems)->firstWhere('active', true) ?? $securityMenuItems[0]; - $activeSecurityMenuValue = (($activeSecurityMenuItem['navigate'] ?? true) ? 'navigate' : 'location').'|security|'.route($activeSecurityMenuItem['route'], $parameters); @endphp -
-
- - -
- - -
+ + diff --git a/resources/views/components/server/sidebar-sentinel.blade.php b/resources/views/components/server/sidebar-sentinel.blade.php index 40962f202..5eee93eba 100644 --- a/resources/views/components/server/sidebar-sentinel.blade.php +++ b/resources/views/components/server/sidebar-sentinel.blade.php @@ -1,121 +1,37 @@ @php - $serverPageItems = [ - [ - 'label' => 'Configuration', - 'route' => 'server.show', - 'active' => request()->routeIs('server.show', 'server.advanced', 'server.private-key', 'server.cloud-provider-token', 'server.ca-certificate', 'server.cloudflare-tunnel', 'server.docker-cleanup', 'server.destinations', 'server.log-drains', 'server.metrics', 'server.swarm', 'server.delete'), - ], - [ - 'label' => 'Proxy', - 'route' => 'server.proxy', - 'active' => request()->routeIs('server.proxy', 'server.proxy.*'), - 'visible' => ! $server->isSwarmWorker() && ! $server->settings->is_build_server, - ], - [ - 'label' => 'Sentinel', - 'route' => 'server.sentinel', - 'active' => request()->routeIs('server.sentinel', 'server.sentinel.*'), - 'visible' => $server->isFunctional() && ! $server->isSwarm() && ! $server->settings->is_build_server && auth()->user()?->can('viewSentinel', $server), - ], - [ - 'label' => 'Resources', - 'route' => 'server.resources', - 'active' => request()->routeIs('server.resources'), - ], - [ - 'label' => 'Terminal', - 'route' => 'server.command', - 'active' => request()->routeIs('server.command'), - 'navigate' => false, - 'visible' => auth()->user()?->can('canAccessTerminal'), - ], - [ - 'label' => 'Security', - 'route' => 'server.security.patches', - 'active' => request()->routeIs('server.security.patches'), - 'visible' => auth()->user()?->can('update', $server), - ], - ]; $sentinelMenuItems = [ [ 'label' => 'Configuration', 'route' => 'server.sentinel', 'active' => request()->routeIs('server.sentinel'), + 'icon' => 'settings', ], [ 'label' => 'Logs', 'route' => 'server.sentinel.logs', 'active' => request()->routeIs('server.sentinel.logs'), + 'icon' => 'file-content', ], ]; - $serverPageItems = array_values(array_filter( - $serverPageItems, - fn (array $item): bool => $item['visible'] ?? true, - )); - $activeSentinelMenuItem = collect($sentinelMenuItems)->firstWhere('active', true) ?? $sentinelMenuItems[0]; - $activeSentinelMenuValue = 'navigate|sentinel|'.route($activeSentinelMenuItem['route'], $parameters); @endphp -
+
+ diff --git a/resources/views/components/server/sidebar.blade.php b/resources/views/components/server/sidebar.blade.php index 68ea08d31..3be9db092 100644 --- a/resources/views/components/server/sidebar.blade.php +++ b/resources/views/components/server/sidebar.blade.php @@ -1,201 +1,130 @@ @php $serverRouteParameters = ['server_uuid' => $server->uuid]; - $serverPageItems = [ - [ - 'label' => 'Configuration', - 'route' => 'server.show', - 'active' => request()->routeIs('server.show', 'server.advanced', 'server.private-key', 'server.cloud-provider-token', 'server.ca-certificate', 'server.cloudflare-tunnel', 'server.docker-cleanup', 'server.destinations', 'server.log-drains', 'server.metrics', 'server.swarm', 'server.delete'), - ], - [ - 'label' => 'Proxy', - 'route' => 'server.proxy', - 'active' => request()->routeIs('server.proxy', 'server.proxy.*'), - 'visible' => ! $server->isSwarmWorker() && ! $server->settings->is_build_server, - ], - [ - 'label' => 'Sentinel', - 'route' => 'server.sentinel', - 'active' => request()->routeIs('server.sentinel', 'server.sentinel.*'), - 'visible' => $server->isFunctional() && ! $server->isSwarm() && ! $server->settings->is_build_server && auth()->user()?->can('viewSentinel', $server), - ], - [ - 'label' => 'Resources', - 'route' => 'server.resources', - 'active' => request()->routeIs('server.resources'), - ], - [ - 'label' => 'Terminal', - 'route' => 'server.command', - 'active' => request()->routeIs('server.command'), - 'navigate' => false, - 'visible' => auth()->user()?->can('canAccessTerminal'), - ], - [ - 'label' => 'Security', - 'route' => 'server.security.patches', - 'active' => request()->routeIs('server.security.patches'), - 'visible' => auth()->user()?->can('update', $server), - ], - ]; $serverMenuItems = [ [ 'label' => 'General', 'route' => 'server.show', 'active' => $activeMenu === 'general', + 'icon' => 'settings', + 'group' => 'Settings', ], [ 'label' => 'Advanced', 'route' => 'server.advanced', 'active' => $activeMenu === 'advanced', + 'icon' => 'grid', + 'group' => 'Settings', 'visible' => $server->isFunctional(), ], [ 'label' => 'Private Key', 'route' => 'server.private-key', 'active' => $activeMenu === 'private-key', + 'icon' => 'keys', + 'group' => 'Settings', ], [ 'label' => 'Cloud Token', 'route' => 'server.cloud-provider-token', 'active' => $activeMenu === 'cloud-provider-token', + 'icon' => 'subscription', + 'group' => 'Settings', 'visible' => (bool) ($server->hetzner_server_id || $server->vultr_instance_id), ], [ 'label' => 'CA Certificate', 'route' => 'server.ca-certificate', 'active' => $activeMenu === 'ca-certificate', + 'icon' => 'file', + 'group' => 'Settings', ], [ 'label' => 'Cloudflare Tunnel', 'route' => 'server.cloudflare-tunnel', 'active' => $activeMenu === 'cloudflare-tunnel', + 'icon' => 'globe', + 'group' => 'Networking', 'visible' => ! $server->isLocalhost(), ], - [ - 'label' => 'Docker Cleanup', - 'route' => 'server.docker-cleanup', - 'active' => $activeMenu === 'docker-cleanup', - 'visible' => $server->isFunctional(), - ], [ 'label' => 'Destinations', 'route' => 'server.destinations', 'active' => $activeMenu === 'destinations', - 'visible' => $server->isFunctional(), - ], - [ - 'label' => 'Log Drains', - 'route' => 'server.log-drains', - 'active' => $activeMenu === 'log-drains', - 'visible' => $server->isFunctional(), - ], - [ - 'label' => 'Metrics', - 'route' => 'server.metrics', - 'active' => $activeMenu === 'metrics', + 'icon' => 'destinations', + 'group' => 'Networking', 'visible' => $server->isFunctional(), ], [ 'label' => 'Swarm', 'route' => 'server.swarm', 'active' => $activeMenu === 'swarm', + 'icon' => 'layers', + 'group' => 'Networking', 'visible' => ! $server->isBuildServer() && ! $server->settings->is_cloudflare_tunnel, ], + [ + 'label' => 'Docker Cleanup', + 'route' => 'server.docker-cleanup', + 'active' => $activeMenu === 'docker-cleanup', + 'icon' => 'storages', + 'group' => 'Operations', + 'visible' => $server->isFunctional(), + ], + [ + 'label' => 'Log Drains', + 'route' => 'server.log-drains', + 'active' => $activeMenu === 'log-drains', + 'icon' => 'notifications', + 'group' => 'Operations', + 'visible' => $server->isFunctional(), + ], + [ + 'label' => 'Metrics', + 'route' => 'server.metrics', + 'active' => $activeMenu === 'metrics', + 'icon' => 'graph', + 'group' => 'Operations', + 'visible' => $server->isFunctional(), + ], [ 'label' => 'Danger', 'route' => 'server.delete', 'active' => $activeMenu === 'danger', - 'visible' => ! $server->isLocalhost(), + 'icon' => 'shield-alert', + 'group' => 'Danger zone', + // Coolify host (id 0) cannot be deleted. Other servers may still use + // host.docker.internal (e.g. Lima VMs) and must keep the Danger menu. + 'visible' => ! $server->is_coolify_host, ], ]; - $serverPageItems = array_values(array_filter( - $serverPageItems, - fn (array $item): bool => $item['visible'] ?? true, - )); - $serverMenuItems = array_values(array_filter( - $serverMenuItems, - fn (array $item): bool => $item['visible'] ?? true, - )); - $activeServerMenuItem = collect($serverMenuItems)->firstWhere('active', true); - $activeServerPageItem = collect($serverPageItems)->firstWhere('active', true); - $activeServerMobileItem = $activeServerMenuItem ?? $activeServerPageItem ?? $serverMenuItems[0]; - $activeServerMobileGroup = $activeServerMenuItem ? 'configuration' : 'server'; - $activeServerMobileNavigation = ($activeServerMobileItem['navigate'] ?? true) ? 'navigate' : 'location'; - $activeServerMenuValue = $activeServerMobileNavigation.'|'.$activeServerMobileGroup.'|'.route($activeServerMobileItem['route'], $serverRouteParameters); + $serverMenuItems = collect($serverMenuItems) + ->filter(fn (array $item): bool => $item['visible'] ?? true) + ->values(); + $groupedServerMenuItems = $serverMenuItems->groupBy('group'); @endphp -
-
- - -
- - -
+ + diff --git a/resources/views/components/service-database/sidebar.blade.php b/resources/views/components/service-database/sidebar.blade.php index 9be795305..890c13fe4 100644 --- a/resources/views/components/service-database/sidebar.blade.php +++ b/resources/views/components/service-database/sidebar.blade.php @@ -4,26 +4,60 @@ 'isImportSupported' => false, ]) - +@php + $items = [ + [ + 'label' => 'General', + 'route' => 'project.service.index', + 'icon' => 'settings', + 'active' => request()->routeIs('project.service.index'), + ], + [ + 'label' => 'Advanced', + 'route' => 'project.service.index.advanced', + 'icon' => 'grid', + 'active' => request()->routeIs('project.service.index.advanced'), + ], + [ + 'label' => 'Backups', + 'route' => 'project.service.database.backups', + 'icon' => 'storages', + 'active' => request()->routeIs('project.service.database.backup*'), + 'visible' => $serviceDatabase?->isBackupSolutionAvailable() || $serviceDatabase?->is_migrated, + ], + [ + 'label' => 'Import Backup', + 'route' => 'project.service.database.import', + 'icon' => 'upload', + 'active' => request()->routeIs('project.service.database.import'), + 'visible' => $isImportSupported, + 'navigate' => false, + ], + ]; + + $items = array_values(array_filter($items, fn (array $item): bool => $item['visible'] ?? true)); +@endphp + + diff --git a/resources/views/components/services/advanced.blade.php b/resources/views/components/services/advanced.blade.php index 1974bf221..dd2b10b7e 100644 --- a/resources/views/components/services/advanced.blade.php +++ b/resources/views/components/services/advanced.blade.php @@ -1,48 +1,51 @@ - - +@php + $status = str($service->status ?? ''); + $canDeploy = auth()->user()->can('deploy', $service); + $canStop = auth()->user()->can('stop', $service); +@endphp + +
+ + + +
diff --git a/resources/views/components/services/links.blade.php b/resources/views/components/services/links.blade.php index 26df8a685..7740b3eab 100644 --- a/resources/views/components/services/links.blade.php +++ b/resources/views/components/services/links.blade.php @@ -1,12 +1,26 @@ +@php + $linkItemClasses = + 'flex items-center gap-2 px-3 h-8 text-[13px] transition-colors text-neutral-600 dark:text-fg-dim hover:bg-neutral-100 dark:hover:bg-white/[0.06] hover:text-black dark:hover:text-fg'; +@endphp + @if ($links->count() > 0) - - +
+ +
+ @foreach ($links as $link) + + + {{ $link }} + + @endforeach +
+
@endif diff --git a/resources/views/components/settings/navbar.blade.php b/resources/views/components/settings/navbar.blade.php index 565e485d0..fefd36717 100644 --- a/resources/views/components/settings/navbar.blade.php +++ b/resources/views/components/settings/navbar.blade.php @@ -1,29 +1,20 @@ -
-

Settings

-
Instance wide settings for Coolify.
- +@props([ + 'title' => 'Settings', + 'subtitle' => 'Instance configuration and maintenance', +]) + +{{-- Same 1180px shell as the workspace. Title hidden only at xl+ (full desktop). --}} +
+ + @isset($titleActions) + + {{ $titleActions }} + + @endisset + @isset($actions) + + {{ $actions }} + + @endisset +
diff --git a/resources/views/components/settings/sidebar.blade.php b/resources/views/components/settings/sidebar.blade.php index 0a68a9155..be17ffee3 100644 --- a/resources/views/components/settings/sidebar.blade.php +++ b/resources/views/components/settings/sidebar.blade.php @@ -1,8 +1,39 @@ - +@php + $settingsMenuItems = [ + [ + 'label' => 'General', + 'route' => 'settings.index', + 'active' => $activeMenu === 'general', + 'icon' => 'settings', + ], + [ + 'label' => 'Advanced', + 'route' => 'settings.advanced', + 'active' => $activeMenu === 'advanced', + 'icon' => 'grid', + ], + [ + 'label' => 'Updates', + 'route' => 'settings.updates', + 'active' => $activeMenu === 'updates', + 'icon' => 'refresh3', + ], + ]; +@endphp + + diff --git a/resources/views/components/shared-variables/editor.blade.php b/resources/views/components/shared-variables/editor.blade.php new file mode 100644 index 000000000..93959581f --- /dev/null +++ b/resources/views/components/shared-variables/editor.blade.php @@ -0,0 +1,137 @@ +@props([ + 'resource', + 'variables', + 'type', + 'title', + 'view', + 'variablesLabel', +]) + +@php + $alphabeticalPositions = $variables->sortBy('key')->values()->pluck('id')->flip(); + $sharedVariableRows = $variables + ->map(fn($variable) => [ + 'key' => mb_strtolower($variable->key), + 'comment' => mb_strtolower($variable->comment ?? ''), + 'scope' => mb_strtolower(str($type)->headline()), + ]) + ->values(); +@endphp + +
+ + + + + {{ $view === 'normal' ? 'Developer view' : 'Normal view' }} + {{ $view === 'normal' ? 'Developer' : 'Normal' }} + + + + + + @if ($view === 'normal') +
+
+ + + +
+ +
+
+ +
+ + +
+
+ + @can('update', $resource) + + + + + + + @endcan +
+
+ + @if ($variables->isEmpty()) + + @else +
+
+ Name + Scope + Comment + Multiline + +
+ @foreach ($variables as $env) + + @endforeach +
+ +
+
+ @endif + @else +
+ + + + @endif +
+
diff --git a/resources/views/components/slide-over.blade.php b/resources/views/components/slide-over.blade.php index 114135463..90e568ae1 100644 --- a/resources/views/components/slide-over.blade.php +++ b/resources/views/components/slide-over.blade.php @@ -1,4 +1,7 @@ @props(['closeWithX' => false, 'fullScreen' => false]) +{{-- `contents` so event-only shells (no trigger in the default slot) do not leave a + ghost layout box above layer-2 tabs. Trigger buttons in the slot still flow + into the parent as if unwrapped. --}}
merge(['class' => 'relative w-auto h-auto']) }}> +{{ $attributes->merge(['class' => 'contents']) }}> {{ $slot }}