From faf527d554136f19df46b9c4b6a4706b2b88bac9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 10 Aug 2026 09:56:30 +0200 Subject: [PATCH 01/19] fix(dev): support configurable hosts and light-mode surfaces Add configurable bind addresses and Vite protocols for development networking, and improve light-mode styling for logs and unsaved-change controls. --- docker-compose.dev.yml | 9 +++++---- resources/css/app.css | 14 ++++++++++++-- .../views/components/unsaved-bar.blade.php | 8 ++++---- .../Feature/DeploymentLogToolbarStyleTest.php | 10 +++++++--- tests/Feature/DeploymentLogsLayoutTest.php | 2 ++ tests/Feature/SentinelUnsavedBarFlashTest.php | 19 +++++++++++++++++++ vite.config.js | 8 +++++++- 7 files changed, 56 insertions(+), 14 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 7519a599b..0aafe6e25 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -13,7 +13,7 @@ services: - COOLIFY_CLI_VERSION=${COOLIFY_CLI_VERSION:-nightly} - COOLIFY_CLI_CHECKSUM=${COOLIFY_CLI_CHECKSUM:-unknown} ports: - - "${APP_PORT:-8000}:8080" + - "${DEV_BIND_ADDRESS:-0.0.0.0}:${APP_PORT:-8000}:8080" - "${FORWARD_FLUX_PORT:-6443}:6443" extra_hosts: - "host.docker.internal:host-gateway" @@ -82,8 +82,8 @@ services: env_file: - .env ports: - - "${FORWARD_SOKETI_PORT:-6001}:6001" - - "6002:6002" + - "${DEV_BIND_ADDRESS:-0.0.0.0}:${FORWARD_SOKETI_PORT:-6001}:6001" + - "${DEV_BIND_ADDRESS:-0.0.0.0}:6002:6002" extra_hosts: - "host.docker.internal:host-gateway" volumes: @@ -111,8 +111,9 @@ services: # Set VITE_HOST in .env to a browser-reachable IP/hostname for LAN/Tailscale access VITE_HOST: "${VITE_HOST:-localhost}" VITE_PORT: "${VITE_PORT:-5173}" + VITE_PROTOCOL: "${VITE_PROTOCOL:-http}" ports: - - "${VITE_PORT:-5173}:${VITE_PORT:-5173}" + - "${DEV_BIND_ADDRESS:-0.0.0.0}:${VITE_PORT:-5173}:${VITE_PORT:-5173}" volumes: - .:/var/www/html/:cached command: sh -c "npm install && npm run dev" diff --git a/resources/css/app.css b/resources/css/app.css index 2a9356412..44bb7213c 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -2673,6 +2673,16 @@ input[type="search"]::-webkit-search-results-decoration { /* Shared logs viewer (deployment + runtime) — mobile-first toolbar Mobile stacks: search → meta → full-width actions (no side-by-side overlap). */ +.logs-viewer { + background: #fff; + color: #262626; +} + +.dark .logs-viewer { + background: var(--color-log); + color: #f5f5f5; +} + .logs-viewer-toolbar { flex-shrink: 0; border-bottom: 1px solid var(--color-neutral-200); @@ -2934,7 +2944,7 @@ html[data-theme="custom"] .logs-viewer-timestamp { .logs-viewer-viewport { min-width: 0; - padding: 0.5rem 0.75rem; + padding: 0.5rem 0.75rem 2rem; } .logs-viewer-line { @@ -3024,7 +3034,7 @@ html[data-theme="custom"] .logs-viewer-timestamp { } .logs-viewer-viewport { - padding: 0.5rem 1rem; + padding: 0.5rem 1rem 2rem; } .logs-viewer-line { diff --git a/resources/views/components/unsaved-bar.blade.php b/resources/views/components/unsaved-bar.blade.php index 9d4ec36be..15b945154 100644 --- a/resources/views/components/unsaved-bar.blade.php +++ b/resources/views/components/unsaved-bar.blade.php @@ -56,18 +56,18 @@ } " @if ($targets) wire:target="{{ $targets }}" @endif - class="pointer-events-none fixed inset-x-3 bottom-[calc(var(--keyboard-inset,0px)+max(1.5rem,env(safe-area-inset-bottom,0px)+0.75rem))] z-[1000] flex max-w-full translate-y-6 flex-col items-stretch gap-2 rounded-2xl border border-white/10 bg-surface py-2.5 pr-2.5 pl-4 opacity-0 shadow-modal transition-[opacity,transform] duration-200 ease-out delay-0 [&.is-dirty]:pointer-events-auto [&.is-dirty]:translate-y-0 [&.is-dirty]:opacity-100 [&.is-dirty]:delay-300 sm:inset-x-auto sm:left-1/2 sm:bottom-6 sm:w-max sm:max-w-none sm:-translate-x-1/2 sm:flex-row sm:items-center sm:gap-8 sm:py-2 sm:pl-5 sm:pr-2"> - {{ $label }} + class="pointer-events-none fixed inset-x-3 bottom-[calc(var(--keyboard-inset,0px)+max(1.5rem,env(safe-area-inset-bottom,0px)+0.75rem))] z-[1000] flex max-w-full translate-y-6 flex-col items-stretch gap-2 rounded-2xl border border-neutral-200 bg-white py-2.5 pr-2.5 pl-4 opacity-0 shadow-modal transition-[opacity,transform] duration-200 ease-out delay-0 dark:border-white/10 dark:bg-surface [&.is-dirty]:pointer-events-auto [&.is-dirty]:translate-y-0 [&.is-dirty]:opacity-100 [&.is-dirty]:delay-300 sm:inset-x-auto sm:left-1/2 sm:bottom-6 sm:w-max sm:max-w-none sm:-translate-x-1/2 sm:flex-row sm:items-center sm:gap-8 sm:py-2 sm:pl-5 sm:pr-2"> + {{ $label }}
diff --git a/tests/Feature/DeploymentLogToolbarStyleTest.php b/tests/Feature/DeploymentLogToolbarStyleTest.php index d425a9164..57764d82d 100644 --- a/tests/Feature/DeploymentLogToolbarStyleTest.php +++ b/tests/Feature/DeploymentLogToolbarStyleTest.php @@ -11,11 +11,15 @@ test('active deployment log controls use a coollabs background and white icon', test('deployment logs use light surfaces in light mode and dark surfaces in dark mode', function () { $view = file_get_contents(resource_path('views/livewire/project/application/deployment/show.blade.php')); + $styles = file_get_contents(resource_path('css/app.css')); expect($view) ->toContain('bg-white text-neutral-800') - ->toContain('dark:bg-[#0d0d0d] dark:text-neutral-100') - ->toContain('border-neutral-200 shadow-sm dark:border-neutral-800') + ->toContain('dark:bg-log dark:text-neutral-100') + ->toContain('border-neutral-200 shadow-sm dark:border-coolgray-200') ->toContain('border-neutral-200! bg-white!') - ->toContain('dark:border-white/[0.08]! dark:bg-white/[0.05]!'); + ->toContain('dark:border-white/[0.08]! dark:bg-white/[0.05]!') + ->and($styles) + ->toMatch('/\.logs-viewer\s*\{[^}]*background:\s*#fff;[^}]*color:\s*#262626;/s') + ->toMatch('/\.dark \.logs-viewer\s*\{[^}]*background:\s*var\(--color-log\);/s'); }); diff --git a/tests/Feature/DeploymentLogsLayoutTest.php b/tests/Feature/DeploymentLogsLayoutTest.php index dae0ee310..7243a7c83 100644 --- a/tests/Feature/DeploymentLogsLayoutTest.php +++ b/tests/Feature/DeploymentLogsLayoutTest.php @@ -117,6 +117,8 @@ it('uses a mobile-friendly stacked logs toolbar markup', function () { ->toContain('.logs-viewer-actions') ->toContain('.logs-viewer-deployment-actions') ->toContain('.logs-settings-section') + ->toContain('padding: 0.5rem 0.75rem 2rem;') + ->toContain('padding: 0.5rem 1rem 2rem;') ->toContain('flex-direction: column') ->toContain('@media (min-width: 640px)'); diff --git a/tests/Feature/SentinelUnsavedBarFlashTest.php b/tests/Feature/SentinelUnsavedBarFlashTest.php index e19cfef92..fe2aaef9b 100644 --- a/tests/Feature/SentinelUnsavedBarFlashTest.php +++ b/tests/Feature/SentinelUnsavedBarFlashTest.php @@ -47,6 +47,25 @@ test('unsaved bar saves with enter and shows the shortcut on the save button', f ->toContain('Enter'); }); +test('unsaved bar keyboard shortcut remains visible in light mode', function () { + $contents = file_get_contents(resource_path('views/components/unsaved-bar.blade.php')); + + expect($contents) + ->toContain('border-coollabs/20 bg-coollabs/10') + ->toContain('text-coollabs-200') + ->toContain('dark:border-white/20 dark:bg-white/10 dark:text-white/75'); +}); + +test('unsaved bar uses a light surface in light mode', function () { + $contents = file_get_contents(resource_path('views/components/unsaved-bar.blade.php')); + + expect($contents) + ->toContain('border-neutral-200 bg-white') + ->toContain('text-neutral-800 dark:text-fg') + ->toContain('bg-neutral-100') + ->toContain('dark:bg-white/[0.07]'); +}); + test('unsaved bar stays above floating notifications so save actions remain accessible', function () { $unsavedBar = file_get_contents(resource_path('views/components/unsaved-bar.blade.php')); $popup = file_get_contents(resource_path('views/components/popup-small.blade.php')); diff --git a/vite.config.js b/vite.config.js index bc2080ebc..3335cd1b5 100644 --- a/vite.config.js +++ b/vite.config.js @@ -16,6 +16,11 @@ export default defineConfig(({ mode }) => { viteHost ).trim(); const vitePort = Number(process.env.VITE_PORT || env.VITE_PORT || 5173); + const viteProtocol = ( + process.env.VITE_PROTOCOL || + env.VITE_PROTOCOL || + "http" + ).trim(); return { resolve: { @@ -34,10 +39,11 @@ export default defineConfig(({ mode }) => { allowedHosts: true, // App (:8000) and Vite (:5173) are different origins; allow any host in dev cors: true, - origin: `http://${viteHost}:${vitePort}`, + origin: `${viteProtocol}://${viteHost}:${vitePort}`, hmr: { host: viteHmrHost, clientPort: vitePort, + protocol: viteProtocol === "https" ? "wss" : "ws", }, }, plugins: [ From 4ba6aa350094373c0a145c5d38e1dde46a39c925 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:09:03 +0200 Subject: [PATCH 02/19] feat(servers): separate connection and provisioning options Group existing server connections separately from cloud provisioning and use a select for dedicated build server roles. --- .../views/livewire/boarding/index.blade.php | 43 +++-- .../views/livewire/server/create.blade.php | 160 +++++++++--------- .../views/livewire/server/new/by-ip.blade.php | 7 +- tests/Feature/ServerCreatePageLayoutTest.php | 39 +++++ .../ServerCreationBuildRoleLayoutTest.php | 4 + 5 files changed, 148 insertions(+), 105 deletions(-) diff --git a/resources/views/livewire/boarding/index.blade.php b/resources/views/livewire/boarding/index.blade.php index f51c82156..2f955f167 100644 --- a/resources/views/livewire/boarding/index.blade.php +++ b/resources/views/livewire/boarding/index.blade.php @@ -95,7 +95,11 @@ Select where to deploy your applications and databases. You can add more servers later. -
+
+
+

Add a server

+

Use this machine or connect a server you already manage.

+
+
+ + @can('viewAny', App\Models\CloudProviderToken::class) +
+

Provision a server

+

Create a server with a cloud provider.

+
@if ($currentState === 'select-server-type')
- - - - + Hetzner

Hetzner Cloud

@@ -170,12 +177,8 @@

- - - - + Vultr

Vultr Cloud

@@ -205,6 +208,8 @@ @endif +

+
@endcan
@@ -421,19 +426,13 @@ -
-

- Non-root user support is experimental. - Learn - more -

Validate Connection diff --git a/resources/views/livewire/server/create.blade.php b/resources/views/livewire/server/create.blade.php index 9b42400bd..b61640fb6 100644 --- a/resources/views/livewire/server/create.blade.php +++ b/resources/views/livewire/server/create.blade.php @@ -15,91 +15,89 @@ @if (!$selectedType) -
+ @else
diff --git a/resources/views/livewire/server/new/by-ip.blade.php b/resources/views/livewire/server/new/by-ip.blade.php index 51b93e99c..14c453da5 100644 --- a/resources/views/livewire/server/new/by-ip.blade.php +++ b/resources/views/livewire/server/new/by-ip.blade.php @@ -83,9 +83,12 @@ helper="Non-root SSH users are experimental." />
- + label="Use as a dedicated build server"> + + + diff --git a/tests/Feature/ServerCreatePageLayoutTest.php b/tests/Feature/ServerCreatePageLayoutTest.php index c8fde5863..3e8a8b48b 100644 --- a/tests/Feature/ServerCreatePageLayoutTest.php +++ b/tests/Feature/ServerCreatePageLayoutTest.php @@ -18,3 +18,42 @@ test('provider pages only show the token action in the account panel', function ->not->toContain('new-server-token-') ->not->toContain('tokenProviderName'); }); + +test('server selection separates existing servers from cloud provisioning', function (string $viewPath) { + $view = file_get_contents(resource_path($viewPath)); + + expect($view) + ->toContain('Add a server') + ->toContain('IP address or domain') + ->toContain('Provision a server') + ->and(strpos($view, 'Add a server'))->toBeLessThan(strpos($view, 'Provision a server')); +})->with([ + 'new server page' => 'views/livewire/server/create.blade.php', + 'onboarding' => 'views/livewire/boarding/index.blade.php', +]); + +test('new server sections have vertical spacing', function () { + $view = file_get_contents(resource_path('views/livewire/server/create.blade.php')); + + expect($view)->toContain('
'); +}); + +test('server selection uses the provider logos', function () { + $newServerView = file_get_contents(resource_path('views/livewire/server/create.blade.php')); + $onboardingView = file_get_contents(resource_path('views/livewire/boarding/index.blade.php')); + + expect($newServerView) + ->toContain('src="https://www.vultr.com/media/logo_ondark.svg"') + ->toContain("src=\"{{ asset('svgs/hetzner.svg') }}\"") + ->and($onboardingView) + ->toContain('src="https://www.vultr.com/media/logo_ondark.svg"') + ->toContain("src=\"{{ asset('svgs/hetzner.svg') }}\""); +}); + +test('new server cards do not show method badges', function () { + $view = file_get_contents(resource_path('views/livewire/server/create.blade.php')); + + expect($view) + ->not->toContain("\n Manual\n") + ->not->toContain("\n Provider\n"); +}); diff --git a/tests/Feature/ServerCreationBuildRoleLayoutTest.php b/tests/Feature/ServerCreationBuildRoleLayoutTest.php index af3271ae4..dd4645712 100644 --- a/tests/Feature/ServerCreationBuildRoleLayoutTest.php +++ b/tests/Feature/ServerCreationBuildRoleLayoutTest.php @@ -6,7 +6,11 @@ test('server creation keeps private key actions together and advanced options co expect($view) ->toContain('class="flex items-end gap-3"') ->toContain('toContain('toContain('label="Use as a dedicated build server"') + ->toContain('') + ->toContain('') + ->not->toContain('toContain('helper="Build servers compile applications but do not host deployments. Enabling this makes the server build-only."'); }); From 62b2fe2330b12f4e72e82a4f20126e98bb9bcb6e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:35:15 +0200 Subject: [PATCH 03/19] feat(ui): add volume backup storage controls and grouped navigation --- app/Livewire/Storage/Resources.php | 72 ++++++++++++++ resources/css/utilities.css | 4 +- .../configuration-sidebar.blade.php | 23 +++-- .../database/configuration-sidebar.blade.php | 16 +-- .../views/components/forms/listbox.blade.php | 97 +++++++++++++++---- .../service/configuration-sidebar.blade.php | 15 +-- .../shared-variables/view-controls.blade.php | 6 +- .../views/livewire/project/index.blade.php | 12 +-- .../livewire/project/resource/index.blade.php | 8 +- .../project/service/configuration.blade.php | 21 ++-- .../views/livewire/project/show.blade.php | 12 +-- .../views/livewire/server/index.blade.php | 6 +- .../shared/list-search-controls.blade.php | 6 +- .../livewire/storage/resources.blade.php | 52 +++++++++- resources/views/livewire/tags/show.blade.php | 8 +- .../Feature/HighlightedButtonStylingTest.php | 2 +- .../ResourceHeadingUnifiedNavbarTest.php | 17 ++-- tests/Feature/ServerIndexViewSwitcherTest.php | 8 +- .../ServiceDatabaseVerticalNavigationTest.php | 47 ++++++--- tests/Feature/TeamScopedBackupStorageTest.php | 53 ++++++++++ 20 files changed, 373 insertions(+), 112 deletions(-) diff --git a/app/Livewire/Storage/Resources.php b/app/Livewire/Storage/Resources.php index 4f39943e4..1e9b80c3a 100644 --- a/app/Livewire/Storage/Resources.php +++ b/app/Livewire/Storage/Resources.php @@ -4,6 +4,7 @@ namespace App\Livewire\Storage; use App\Models\S3Storage; use App\Models\ScheduledDatabaseBackup; +use App\Models\ScheduledVolumeBackup; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; @@ -15,6 +16,8 @@ class Resources extends Component public array $selectedStorages = []; + public array $selectedVolumeStorages = []; + public function mount(): void { $this->authorize('view', $this->storage); @@ -26,6 +29,13 @@ class Resources extends Component foreach ($backups as $backup) { $this->selectedStorages[$backup->id] = $this->storage->id; } + + ScheduledVolumeBackup::query() + ->where('s3_storage_id', $this->storage->id) + ->where('save_s3', true) + ->each(function (ScheduledVolumeBackup $backup): void { + $this->selectedVolumeStorages[$backup->id] = $this->storage->id; + }); } public function disableS3(int $backupId): void @@ -80,6 +90,61 @@ class Resources extends Component $this->dispatch('success', 'Backup moved.', "Moved to {$newStorage->name}."); } + public function disableVolumeS3(int $backupId): void + { + $this->authorize('update', $this->storage); + + $backup = ScheduledVolumeBackup::query() + ->where('id', $backupId) + ->where('s3_storage_id', $this->storage->id) + ->firstOrFail(); + + $backup->update([ + 'save_s3' => false, + 's3_storage_id' => null, + ]); + + unset($this->selectedVolumeStorages[$backupId]); + + $this->dispatch('success', 'S3 disabled.', 'S3 backup has been disabled for this schedule.'); + } + + public function moveVolumeBackup(int $backupId): void + { + $this->authorize('update', $this->storage); + + $backup = ScheduledVolumeBackup::query() + ->where('id', $backupId) + ->where('s3_storage_id', $this->storage->id) + ->firstOrFail(); + $newStorageId = $this->selectedVolumeStorages[$backupId] ?? null; + + if (! $newStorageId || (int) $newStorageId === $this->storage->id) { + $this->dispatch('error', 'No change.', 'The backup is already using this storage.'); + + return; + } + + $newStorage = S3Storage::query() + ->where('id', $newStorageId) + ->where('team_id', $this->storage->team_id) + ->first(); + + if (! $newStorage) { + $this->dispatch('error', 'Storage not found.'); + + return; + } + + $this->authorize('update', $newStorage); + + $backup->update(['s3_storage_id' => $newStorage->id]); + + unset($this->selectedVolumeStorages[$backupId]); + + $this->dispatch('success', 'Backup moved.', "Moved to {$newStorage->name}."); + } + public function render() { $backups = ScheduledDatabaseBackup::where('s3_storage_id', $this->storage->id) @@ -92,8 +157,15 @@ class Resources extends Component ->orderBy('name') ->get(['id', 'name', 'is_usable']); + $volumeBackups = ScheduledVolumeBackup::query() + ->where('s3_storage_id', $this->storage->id) + ->where('save_s3', true) + ->with('backupable.resource') + ->get(); + return view('livewire.storage.resources', [ 'groupedBackups' => $backups, + 'volumeBackups' => $volumeBackups, 'allStorages' => $allStorages, ]); } diff --git a/resources/css/utilities.css b/resources/css/utilities.css index bd69dedee..8be3a06e4 100644 --- a/resources/css/utilities.css +++ b/resources/css/utilities.css @@ -131,11 +131,11 @@ } @utility button-highlighted { - @apply border-2 text-coollabs-200 dark:text-white bg-coollabs-50 dark:bg-coollabs/20 border-coollabs dark:border-coollabs-100 hover:bg-coollabs hover:text-white dark:hover:bg-coollabs-100 dark:hover:text-white; + @apply border-coollabs-200 bg-linear-to-b from-coollabs-100 to-coollabs-200 text-white! hover:from-coollabs-100 hover:to-coollabs hover:text-white!; } @utility control-selected { - @apply bg-coollabs text-white dark:bg-coollabs dark:text-white; + @apply bg-linear-to-b from-coollabs-100 to-coollabs-200 text-white; } @utility loading-indicator { diff --git a/resources/views/components/application/configuration-sidebar.blade.php b/resources/views/components/application/configuration-sidebar.blade.php index fe70c8cdb..078f3cafa 100644 --- a/resources/views/components/application/configuration-sidebar.blade.php +++ b/resources/views/components/application/configuration-sidebar.blade.php @@ -52,12 +52,12 @@ 'visible' => ! $application->destination->server->isSwarm() && auth()->user()?->can('canAccessTerminal'), ], [ - 'label' => 'Deployment', + 'label' => 'Deployment Logs', 'route' => 'project.application.deployment.index', 'active' => str($currentRoute)->startsWith('project.application.deployment'), ], [ - 'label' => 'Runtime', + 'label' => 'Runtime Logs', 'route' => 'project.application.logs', 'active' => $currentRoute === 'project.application.logs', ], @@ -142,8 +142,8 @@ 'Persistent Storage' => 'storages', 'Backups' => 'database', 'Terminal' => 'browser-terminal', - 'Deployment' => 'time-back', - 'Runtime' => 'unordered-list', + 'Deployment Logs' => 'time-back', + 'Runtime Logs' => 'unordered-list', 'Git Source' => 'sources', 'Servers' => 'servers', 'Scheduled Tasks' => 'calendar', @@ -160,14 +160,17 @@ // Discord-style groups for the settings sidebar $menuGroups = [ - 'Settings' => ['General', 'Domains', 'Advanced', 'Swarm', 'Environment Variables', 'Persistent Storage', 'Backups'], - 'Build & deploy' => ['Git Source', 'Servers', 'Healthcheck'], - 'Automation' => ['Scheduled Tasks', 'Webhooks', 'Preview Deployments'], - 'Logs' => ['Deployment', 'Runtime'], - 'Operations' => ['Terminal', 'Rollback', 'Resource Limits', 'Resource Operations', 'Metrics', 'Tags', 'Danger Zone'], + 'Settings' => ['General', 'Domains', 'Environment Variables', 'Persistent Storage', 'Advanced', 'Swarm', 'Healthcheck'], + 'Observe & troubleshoot' => ['Runtime Logs', 'Deployment Logs', 'Terminal', 'Metrics'], + 'Deploy' => ['Git Source', 'Servers', 'Preview Deployments'], + 'Automation' => ['Scheduled Tasks', 'Webhooks', 'Backups'], + 'Operations' => ['Resource Operations', 'Resource Limits', 'Rollback', 'Tags', 'Danger Zone'], ]; $groupedMenuItems = collect($menuGroups) - ->map(fn (array $labels) => collect($configurationMenuItems)->whereIn('label', $labels)->values()) + ->map(fn (array $labels) => collect($labels) + ->map(fn (string $label) => collect($configurationMenuItems)->firstWhere('label', $label)) + ->filter() + ->values()) ->filter(fn ($items) => $items->isNotEmpty()); // In-page sections (cards) shown as sub-items under the active page diff --git a/resources/views/components/database/configuration-sidebar.blade.php b/resources/views/components/database/configuration-sidebar.blade.php index a2f301230..2c62641f8 100644 --- a/resources/views/components/database/configuration-sidebar.blade.php +++ b/resources/views/components/database/configuration-sidebar.blade.php @@ -14,7 +14,7 @@ ['label' => 'Backups', 'route' => 'project.database.backup.index', 'icon' => 'database', 'visible' => $database->isBackupSolutionAvailable()], ['label' => 'Import Backup', 'route' => 'project.database.import-backup', 'icon' => 'upload', 'navigate' => false, 'visible' => auth()->user()?->can('update', $database)], ['label' => 'Servers', 'route' => 'project.database.servers', 'icon' => 'servers'], - ['label' => 'Runtime', 'route' => 'project.database.logs', 'icon' => 'unordered-list', 'navigate' => false], + ['label' => 'Runtime Logs', 'route' => 'project.database.logs', 'icon' => 'unordered-list', 'navigate' => false], ['label' => 'Terminal', 'route' => 'project.database.command', 'icon' => 'browser-terminal', 'navigate' => false, 'visible' => auth()->user()?->can('canAccessTerminal')], ['label' => 'Webhooks', 'route' => 'project.database.webhooks', 'icon' => 'notifications'], ['label' => 'Healthcheck', 'route' => 'project.database.healthcheck', 'icon' => 'feedback'], @@ -32,14 +32,18 @@ ]); $menuGroups = [ - 'Settings' => ['General', 'Environment Variables', 'Persistent Storage', 'Backups', 'Import Backup', 'Servers'], - 'Automation' => ['Webhooks', 'Healthcheck'], - 'Logs' => ['Runtime'], - 'Operations' => ['Terminal', 'Resource Limits', 'Resource Operations', 'Metrics', 'Tags', 'Danger Zone'], + 'Settings' => ['General', 'Environment Variables', 'Persistent Storage', 'Healthcheck'], + 'Observe & troubleshoot' => ['Runtime Logs', 'Terminal', 'Metrics'], + 'Deploy' => ['Servers'], + 'Automation' => ['Webhooks', 'Backups', 'Import Backup'], + 'Operations' => ['Resource Operations', 'Resource Limits', 'Tags', 'Danger Zone'], ]; $groupedItems = collect($menuGroups) - ->map(fn (array $labels) => $configurationItems->whereIn('label', $labels)->values()) + ->map(fn (array $labels) => collect($labels) + ->map(fn (string $label) => $configurationItems->firstWhere('label', $label)) + ->filter() + ->values()) ->filter(fn ($items) => $items->isNotEmpty()); $pageSections = $database->type() === 'standalone-postgresql' diff --git a/resources/views/components/forms/listbox.blade.php b/resources/views/components/forms/listbox.blade.php index 0dd64ccf2..4da47bee0 100644 --- a/resources/views/components/forms/listbox.blade.php +++ b/resources/views/components/forms/listbox.blade.php @@ -13,10 +13,12 @@ 'value' => null, // initial value when wire=false 'disabled' => false, 'tooltip' => true, + 'portal' => false, ]) @php $triggerId = ($htmlId ?? $id).'-trigger'; + $panelId = ($htmlId ?? $id).'-panel'; @endphp
@@ -41,6 +43,7 @@ @endif
whereStartsWith('x-model') }} {{ $attributes->whereStartsWith('x-effect') }} - @click.outside="open = false" @keydown.escape="open = false"> - -
-
- {{ $emptyText }} -
-