diff --git a/DESIGN.md b/DESIGN.md index 69f4f91ef..72ab85abe 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -627,6 +627,16 @@ Application and server browser terminals use the same browser-oriented console shell, theme picker, compact header controls, and outline `browser-terminal` Reicon. Hide a container switcher when only one container exists. +The themed console shell belongs to an open session. Before a target is +selected, the global Terminal page stays a normal top-level destination: a +full-width layer card titled `Start a terminal session`, its filter input in +the card header actions, and grouped `Servers` / `Containers` rows reusing the +command-palette row classes. Do not render an empty full-height console canvas +just to host the target picker, and do not offer the console theme selector +before a session owns that canvas. Rows show the target name, a muted server +column that only appears when the team has more than one server, and the shared +chevron. Group headers stick to the top of the scrolling list and carry a count. + ### Logs Runtime and deployment logs should feel like a clean terminal surface: diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 06b9f2d24..601c364de 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -2884,6 +2884,10 @@ class ApplicationsController extends Controller ], 422); } + $requestHasHttpBasicAuth = $request->has('is_http_basic_auth_enabled') + || $request->has('http_basic_auth_username') + || $request->has('http_basic_auth_password'); + if ($request->has('is_http_basic_auth_enabled') && $request->is_http_basic_auth_enabled === true) { if (blank($application->http_basic_auth_username) || blank($application->http_basic_auth_password)) { $validationErrors = []; @@ -2901,10 +2905,6 @@ class ApplicationsController extends Controller } } } - if ($request->has('is_http_basic_auth_enabled') && $application->is_container_label_readonly_enabled === false) { - $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); - $application->save(); - } // For dockercompose applications, domains (fqdn) field should not be used // Only docker_compose_domains should be used to set domains for individual services @@ -3119,7 +3119,7 @@ class ApplicationsController extends Controller // Must run after fqdn is filled: flags are kept only for domains the app still has. $application->setNoindexDomains($request->input('noindex_domains') ?? []); } - if ($application->settings->is_container_label_readonly_enabled && ($requestHasDomains || $requestHasNoindexDomains) && $server->isProxyShouldRun()) { + if ($application->settings->is_container_label_readonly_enabled && ($requestHasDomains || $requestHasNoindexDomains || $requestHasHttpBasicAuth) && $server->isProxyShouldRun()) { $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); } $application->save(); diff --git a/resources/css/app.css b/resources/css/app.css index 53565c508..9dd88d1dd 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -727,6 +727,86 @@ html:not(.dark) .application-console-shell[data-console-theme="system"] .termina backdrop-filter: blur(16px); } +/* Pre-session target list: a normal page card, not the themed console canvas. */ +.terminal-target-card .application-settings-section-body { + overflow: hidden; +} + +/* The header inset is tuned for 32px buttons. A full-width filter needs the + same gutter on both sides once the actions row wraps below the title. + Matches the base header selector's specificity so the shorthand cannot win. */ +@media (max-width: 640px) { + .application-settings-section.terminal-target-card > :is(header, .application-settings-section-header) { + padding-right: 1rem; + } +} + +.terminal-target-card-list { + max-height: min(70vh, 34rem); + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--coollabs-fill) transparent; +} + +.terminal-target-card-list::-webkit-scrollbar { + width: 8px; +} + +.terminal-target-card-list::-webkit-scrollbar-track { + background: transparent; +} + +.terminal-target-card-list::-webkit-scrollbar-thumb { + border: 2px solid transparent; + border-radius: 9999px; + background-color: var(--coollabs-fill); + background-clip: padding-box; +} + +/* Group headers stay readable while scrolling long container lists. */ +.terminal-target-group-label { + display: flex; + position: sticky; + z-index: 1; + top: 0; + align-items: center; + gap: 0.375rem; + padding: 0.5rem 0.5rem 0.375rem; + background: var(--coollabs-base); + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.01em; + color: var(--coollabs-subtle); +} + +.terminal-target-group-count { + border-radius: 9999px; + background: var(--coollabs-fill); + padding: 0 0.375rem; + font-size: 0.625rem; + line-height: 1rem; + font-variant-numeric: tabular-nums; +} + +/* The server column only earns its space once the row is wide enough. */ +.terminal-target-item-server { + display: none; + max-width: 14rem; + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 0.6875rem; + line-height: 1rem; + color: var(--coollabs-subtle); +} + +@media (min-width: 640px) { + .terminal-target-item-server { + display: block; + } +} + .terminal-session-panel { border: 0; border-radius: 0.75rem; diff --git a/resources/js/terminal.js b/resources/js/terminal.js index 766bd5f86..097a499c4 100644 --- a/resources/js/terminal.js +++ b/resources/js/terminal.js @@ -934,6 +934,7 @@ export function initializeTerminalComponent() { tab: '\t', escape: '\x1b', ctrlC: '\x03', + ctrlD: '\x04', ctrlBackslash: '\x1c', ctrlS: '\x13', ctrlZ: '\x1a' diff --git a/resources/views/livewire/project/shared/terminal.blade.php b/resources/views/livewire/project/shared/terminal.blade.php index e219ceef5..b4a0961a0 100644 --- a/resources/views/livewire/project/shared/terminal.blade.php +++ b/resources/views/livewire/project/shared/terminal.blade.php @@ -76,7 +76,7 @@ : 'terminal-host h-[510px] max-h-[calc(100dvh-10rem)] overflow-hidden px-2 py-1 rounded-sm bg-black')"> -
@@ -96,6 +96,7 @@ + diff --git a/resources/views/livewire/terminal/index.blade.php b/resources/views/livewire/terminal/index.blade.php index 8694aa0bf..6ccc7b9d0 100644 --- a/resources/views/livewire/terminal/index.blade.php +++ b/resources/views/livewire/terminal/index.blade.php @@ -22,6 +22,8 @@ $terminalOptions[] = [ 'value' => $server->uuid, 'label' => $server->name.' · Server', + 'name' => $server->name, + 'server' => $server->name, 'type' => 'server', ]; @@ -30,6 +32,8 @@ $terminalOptions[] = [ 'value' => $container['uuid'], 'label' => $server->name.' · '.$container['name'], + 'name' => $container['name'], + 'server' => $server->name, 'type' => 'container', ]; } @@ -41,7 +45,8 @@ ?? 'Select a server or container'; @endphp -
+
Terminal | Coolify @@ -57,11 +62,12 @@

-
@if ($selected_uuid === 'default') -
-
- -
-
-
-

Start a terminal session

-

- {{ $isLoadingContainers ? 'Finding available servers and containers…' : 'Choose a server or container. The terminal will open after you select a target.' }} -

- @if (! $isLoadingContainers && $servers->isNotEmpty()) -
+
+ + @if (! $isLoadingContainers && $servers->isNotEmpty()) + +
- + class="pointer-events-none absolute top-1/2 left-2.5 z-10 size-3.5 -translate-y-1/2 text-neutral-400 dark:text-fg-faint" /> + +
- @endif -
-
- @if ($isLoadingContainers) -
-
- - Loading servers and containers… -
-
- @elseif ($servers->isEmpty()) -
-
No terminal targets available
-
Connect a reachable server and enable terminal access.
-
- @else + + @endif + + @if ($isLoadingContainers) +
+ + Finding available servers and containers… +
+ @elseif ($servers->isEmpty()) + + @else +
+ class="px-3 py-8 text-center text-[13px] text-neutral-500 dark:text-fg-dim"> No matching targets
- @endif -
-
-
+
+ @endif + +
@else
-
-
+