Merge remote-tracking branch 'origin/main'

This commit is contained in:
Andras Bacsai committed 2026-08-12 16:42:47 +02:00
commit fb4588be3c
9 files changed
+271 -90

No files matched your search

+80
View File
@@ -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;
+1
View File
@@ -934,6 +934,7 @@ export function initializeTerminalComponent() {
tab: '\t',
escape: '\x1b',
ctrlC: '\x03',
ctrlD: '\x04',
ctrlBackslash: '\x1c',
ctrlS: '\x13',
ctrlZ: '\x1a'
@@ -76,7 +76,7 @@
: 'terminal-host h-[510px] max-h-[calc(100dvh-10rem)] overflow-hidden px-2 py-1 rounded-sm bg-black')">
</div>
<div x-show="terminalActive" x-cloak
<div x-show="terminalActive" x-cloak class="sm:hidden"
:class="fullscreen ? 'relative z-[2] shrink-0 px-2 pb-2' : (keyboardInset > 0 ? 'fixed inset-x-0 z-[100002] px-2 pb-2' : 'relative z-[2] mt-2 shrink-0')"
:style="!fullscreen && keyboardInset > 0 ? `top: ${keyboardAnchorTop}px; transform: translateY(-100%)` : ''"
data-terminal-mobile-toolbar>
@@ -96,6 +96,7 @@
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalKey('~')">~</button>
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalKey('-')">-</button>
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('ctrlC')">^C</button>
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('ctrlD')">^D</button>
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('ctrlBackslash')">^\</button>
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('ctrlS')">^S</button>
<button type="button" class="terminal-mobile-key" x-on:click="sendTerminalControl('ctrlZ')">^Z</button>
@@ -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
<div class="terminal-page application-settings-form" x-init="$wire.loadContainers()">
<div class="{{ $selected_uuid === 'default' ? '' : 'terminal-page' }} application-settings-form"
x-init="$wire.loadContainers()">
<x-slot:title>
Terminal | Coolify
</x-slot>
@@ -57,11 +62,12 @@
</p>
</header>
<section class="terminal-page-console min-h-0 w-full flex-1 overflow-hidden"
<section class="{{ $selected_uuid === 'default' ? 'w-full' : 'terminal-page-console min-h-0 w-full flex-1 overflow-hidden' }}"
x-on:terminal-theme-selected="setTheme($event.detail.theme)"
x-on:terminal-starting.window="syncTheme()"
x-data="{
targets: @js($terminalOptions),
multipleServers: @js($servers->count() > 1),
currentTargetLabel: @js($terminalLabel),
targetChosen: @js($selected_uuid !== 'default'),
targetOpen: false,
@@ -106,72 +112,68 @@
}
}">
@if ($selected_uuid === 'default')
<div wire:key="terminal-target-canvas" data-terminal-target-canvas
class="application-console-shell relative flex h-full min-h-0 w-full items-center justify-center overflow-hidden rounded-lg p-3 sm:p-6"
:data-console-theme="consoleTheme"
:style="{ '--terminal-scrollbar': themeAccents[consoleTheme] }">
<div class="absolute top-3 right-3 z-20">
<x-terminal.theme-selector :themes="$consoleThemes" :theme-names="$consoleThemeNames"
:theme-accents="$consoleThemeAccents" />
</div>
<div data-terminal-target-picker="page"
class="terminal-target-picker z-10 flex max-h-full w-full max-w-lg flex-col overflow-hidden rounded-lg border shadow-[0_18px_50px_rgba(0,0,0,0.28)]">
<div class="shrink-0 border-b border-white/[0.08] p-4">
<h2 class="text-base font-semibold text-white/85">Start a terminal session</h2>
<p class="mt-1 text-sm text-white/55">
{{ $isLoadingContainers ? 'Finding available servers and containers…' : 'Choose a server or container. The terminal will open after you select a target.' }}
</p>
@if (! $isLoadingContainers && $servers->isNotEmpty())
<div class="relative mt-3">
<div wire:key="terminal-target-canvas" data-terminal-target-canvas
class="application-settings-workspace flex w-full min-w-0 flex-col">
<x-application.settings-section class="terminal-target-card" data-terminal-target-picker="page"
title="Start a terminal session" flush>
@if (! $isLoadingContainers && $servers->isNotEmpty())
<x-slot:actions>
<div class="relative w-full sm:w-64">
<x-reicon name="search"
class="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-white/40" />
<input x-model.debounce.100ms="targetSearch" type="search" placeholder="Filter targets…"
class="w-full rounded-md! border-white/[0.1]! bg-black/15! py-2! pr-3! pl-9! text-sm! text-white! shadow-none! placeholder:text-white/35 focus:border-white/25! focus:ring-0!">
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" />
<input x-model.debounce.100ms="targetSearch" type="search" placeholder="Filter targets"
aria-label="Filter terminal targets"
class="h-8! w-full rounded-lg! border-neutral-200! bg-white! py-0! pr-8! pl-8! text-[12px]! shadow-none! placeholder:text-neutral-400 focus:border-accent! focus:ring-0! dark:border-white/[0.08]! dark:bg-white/[0.035]! dark:text-fg! dark:placeholder:text-fg-faint">
<button x-cloak x-show="targetSearch" x-on:click="targetSearch = ''" type="button"
class="absolute top-1/2 right-2 flex size-5 -translate-y-1/2 items-center justify-center rounded text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-black dark:text-fg-faint dark:hover:bg-white/[0.07] dark:hover:text-fg"
aria-label="Clear target filter">
<span class="text-sm leading-none">×</span>
</button>
</div>
@endif
</div>
<div class="terminal-target-list min-h-0 flex-1 overflow-y-auto p-2">
@if ($isLoadingContainers)
<div class="flex min-h-28 items-center justify-center">
<div class="terminal-loading-label flex items-center gap-2">
<x-loading class="size-4" />
<span>Loading servers and containers…</span>
</div>
</div>
@elseif ($servers->isEmpty())
<div class="px-3 py-8 text-center">
<div class="text-sm font-medium text-white/70">No terminal targets available</div>
<div class="mt-1 text-xs text-white/45">Connect a reachable server and enable terminal access.</div>
</div>
@else
</x-slot:actions>
@endif
@if ($isLoadingContainers)
<div class="flex min-h-40 items-center justify-center gap-2 text-[13px] text-neutral-500 dark:text-fg-dim">
<x-loading class="size-4" />
<span>Finding available servers and containers…</span>
</div>
@elseif ($servers->isEmpty())
<x-empty title="No terminal targets available"
description="Connect a reachable server and enable terminal access to start a session."
icon-name="browser-terminal" />
@else
<div class="terminal-target-card-list">
<template x-for="group in filteredTargetGroups" :key="group.type">
<section class="not-last:mb-2">
<div class="px-3 py-2 text-[10px] font-semibold tracking-wider text-white/40 uppercase"
x-text="group.label"></div>
<section class="command-palette-section">
<div class="terminal-target-group-label">
<span x-text="group.label"></span>
<span class="terminal-target-group-count" x-text="group.targets.length"></span>
</div>
<template x-for="target in group.targets" :key="target.value">
<button type="button"
class="flex min-h-11 w-full items-center gap-3 rounded-md px-3 text-left text-sm text-white/70 transition-colors hover:bg-white/[0.07] hover:text-white"
<button type="button" class="command-palette-item cursor-pointer"
x-on:click="selectTarget(target)">
<x-reicon name="servers" x-show="target.type === 'server'"
class="size-4 shrink-0 text-white/40" />
<x-reicon name="layers" x-show="target.type === 'container'"
class="size-4 shrink-0 text-white/40" />
<span class="min-w-0 flex-1 truncate" x-text="target.label"></span>
<span class="rounded-full border border-white/10 px-2 py-0.5 text-[10px] text-white/40"
x-text="target.type"></span>
<x-reicon name="arrow-right" class="size-3.5 shrink-0 text-white/35" />
<x-reicon name="servers" x-cloak x-show="target.type === 'server'"
class="size-3.5 shrink-0 text-neutral-400 dark:text-fg-faint" />
<x-reicon name="layers" x-cloak x-show="target.type === 'container'"
class="size-3.5 shrink-0 text-neutral-400 dark:text-fg-faint" />
<span class="command-palette-item-name min-w-0 flex-1"
x-text="target.name"></span>
<span x-cloak x-show="multipleServers"
class="terminal-target-item-server" x-text="target.server"></span>
<x-reicon name="arrow-right" class="command-palette-item-chevron" />
</button>
</template>
</section>
</template>
<div x-show="filteredTargetGroups.length === 0"
class="px-3 py-8 text-center text-sm text-white/50">
class="px-3 py-8 text-center text-[13px] text-neutral-500 dark:text-fg-dim">
No matching targets
</div>
@endif
</div>
</div>
</div>
</div>
@endif
</x-application.settings-section>
</div>
@else
<div wire:key="terminal-session-canvas" data-terminal-session-canvas
class="application-console-shell flex h-full min-h-0 flex-col overflow-hidden rounded-lg p-3 sm:p-6"
@@ -237,8 +239,6 @@
<x-reicon name="layers" x-cloak x-show="target.type === 'container'"
class="size-3.5 shrink-0 text-white/35" />
<span class="min-w-0 flex-1 truncate" x-text="target.label"></span>
<span class="rounded-full border border-white/10 px-1.5 py-0.5 text-[9px] text-white/35"
x-text="target.type"></span>
<svg x-show="$wire.selected_uuid === target.value"
class="size-3 text-[#fcd452]" viewBox="0 0 12 12" fill="none"
aria-hidden="true">
@@ -301,22 +301,22 @@
class="h-8! w-full rounded-md! border-white/[0.08]! bg-white/[0.05]! py-0! pr-2! pl-7! text-[11px]! text-white! shadow-none! placeholder:text-white/30 focus:border-accent! focus:ring-0!">
</div>
</div>
<div class="max-h-72 overflow-y-auto p-1">
<div class="terminal-target-list max-h-72 overflow-y-auto p-1">
<template x-for="group in filteredTargetGroups" :key="group.type">
<section class="not-last:mb-1">
<div class="px-2 py-1.5 text-[9px] font-semibold tracking-wider text-white/35 uppercase"
x-text="group.label"></div>
<template x-for="target in group.targets" :key="target.value">
<button type="button"
class="flex min-h-9 w-full items-center gap-2 rounded-md px-2 text-left text-[11px] text-white/65 transition-colors hover:bg-white/[0.07] hover:text-white"
class="group flex min-h-9 w-full cursor-pointer items-center gap-2 rounded-md px-2 text-left text-[11px] text-white/65 transition-colors hover:bg-white/[0.07] hover:text-white"
x-on:click="selectTarget(target)">
<x-reicon name="servers" x-show="target.type === 'server'"
class="size-3.5 shrink-0 text-white/35" />
<x-reicon name="layers" x-show="target.type === 'container'"
class="size-3.5 shrink-0 text-white/35" />
<span class="min-w-0 flex-1 truncate" x-text="target.label"></span>
<span class="rounded-full border border-white/10 px-1.5 py-0.5 text-[9px] text-white/35"
x-text="target.type"></span>
<x-reicon name="arrow-right"
class="size-3 shrink-0 text-white/40 opacity-0 transition-opacity group-hover:opacity-100" />
</button>
</template>
</section>