mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-19 22:23:42 +00:00
fix(realtime): force-WS option and polish connection popup UI
Add PUSHER_FORCE_WS so Echo/Pusher can prefer plain WebSocket transports, redesign the real-time connection warning popup, and give copy-button inputs durable right padding in settings forms.
This commit is contained in:
@@ -42,6 +42,8 @@ return [
|
||||
'host' => env('PUSHER_HOST'),
|
||||
'port' => env('PUSHER_PORT'),
|
||||
'app_key' => env('PUSHER_APP_KEY'),
|
||||
'scheme' => env('PUSHER_SCHEME', 'http'),
|
||||
'force_ws' => filter_var(env('PUSHER_FORCE_WS', false), FILTER_VALIDATE_BOOLEAN),
|
||||
],
|
||||
|
||||
'migration' => [
|
||||
|
||||
@@ -21,6 +21,7 @@ services:
|
||||
PUSHER_HOST: "${PUSHER_HOST:-}"
|
||||
PUSHER_PORT: "${PUSHER_PORT:-}"
|
||||
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
|
||||
PUSHER_FORCE_WS: "${PUSHER_FORCE_WS:-false}"
|
||||
PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
PUSHER_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||
|
||||
@@ -29,6 +29,7 @@ services:
|
||||
PUSHER_HOST: "${PUSHER_HOST:-}"
|
||||
PUSHER_PORT: "${PUSHER_PORT:-}"
|
||||
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
|
||||
PUSHER_FORCE_WS: "${PUSHER_FORCE_WS:-false}"
|
||||
PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
PUSHER_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||
|
||||
@@ -364,6 +364,11 @@ tr td:first-child {
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
/* Same durable clearance for copy-affordance inputs (utility pr-* loses to settings CSS). */
|
||||
.input.input-with-copy-button {
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
.lds-heart {
|
||||
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
@@ -1075,11 +1080,13 @@ body.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-too
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Keep password toggle clear of the value inside denser settings inputs */
|
||||
/* Keep password toggle / copy button clear of the value inside denser settings inputs */
|
||||
.application-settings-workspace .input.input-with-password-toggle,
|
||||
.application-settings-workspace .input[type="password"],
|
||||
.application-settings-workspace .input.input-with-copy-button,
|
||||
.application-settings-form .input.input-with-password-toggle,
|
||||
.application-settings-form .input[type="password"] {
|
||||
.application-settings-form .input[type="password"],
|
||||
.application-settings-form .input.input-with-copy-button {
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<x-reicon name="eye"
|
||||
x-show="! expandedRows['{{ $changeKey }}']"
|
||||
class="size-3.5" />
|
||||
<x-reicon name="eye-off"
|
||||
<x-reicon name="eye-off2"
|
||||
x-show="expandedRows['{{ $changeKey }}']"
|
||||
x-cloak class="size-3.5" />
|
||||
</button>
|
||||
|
||||
@@ -6,21 +6,22 @@
|
||||
@endif
|
||||
<div class="relative">
|
||||
<input type="text" value="{{ $text }}"
|
||||
class="input pr-11 bg-white dark:bg-coolgray-100 dark:read-only:bg-coolgray-100 dark:read-only:text-white"
|
||||
class="input input-with-copy-button bg-white dark:bg-coolgray-100 dark:read-only:bg-coolgray-100 dark:read-only:text-white"
|
||||
readonly
|
||||
@keydown.prevent @paste.prevent @cut.prevent @drop.prevent
|
||||
@focus="$event.target.select()">
|
||||
<button
|
||||
x-show="isSecure"
|
||||
type="button"
|
||||
@click.prevent="copied = true; navigator.clipboard.writeText({{ Js::from($text) }}); setTimeout(() => copied = false, 1000)"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 rounded-sm p-1.5 text-neutral-500 transition-colors hover:text-neutral-700 focus-visible:ring-2 focus-visible:ring-coollabs focus-visible:ring-offset-2 dark:text-neutral-400 dark:hover:text-white dark:focus-visible:ring-warning dark:focus-visible:ring-offset-base"
|
||||
class="copy-button flex absolute inset-y-0 right-0 z-10 items-center pr-2 cursor-pointer text-neutral-500 transition-colors hover:text-black focus-visible:ring-2 focus-visible:ring-coollabs focus-visible:ring-offset-2 dark:text-neutral-400 dark:hover:text-white dark:focus-visible:ring-warning dark:focus-visible:ring-offset-base"
|
||||
title="Copy to clipboard"
|
||||
aria-label="Copy to clipboard">
|
||||
<svg x-show="!copied" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg x-show="!copied" class="size-[18px]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<svg x-show="copied" class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg x-show="copied" class="size-[18px] text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
@@ -171,7 +171,15 @@
|
||||
}
|
||||
}
|
||||
@auth
|
||||
@php
|
||||
$pusherForceWs = (bool) config('constants.pusher.force_ws');
|
||||
@endphp
|
||||
window.Pusher = Pusher;
|
||||
@if ($pusherForceWs)
|
||||
if (window.Pusher && window.Pusher.Runtime) {
|
||||
window.Pusher.Runtime.getProtocol = function () { return 'http:'; };
|
||||
}
|
||||
@endif
|
||||
const EchoConstructor = typeof Echo === 'function' ? Echo : Echo.default;
|
||||
window.Echo = new EchoConstructor({
|
||||
broadcaster: 'pusher',
|
||||
@@ -181,13 +189,11 @@
|
||||
wsPort: "{{ getRealtime() }}",
|
||||
wssPort: "{{ getRealtime() }}",
|
||||
forceTLS: false,
|
||||
encrypted: true,
|
||||
encrypted: @json($pusherForceWs ? false : true),
|
||||
enableStats: false,
|
||||
enableLogging: true,
|
||||
enabledTransports: ['ws', 'wss'],
|
||||
disableStats: true,
|
||||
// Add auto reconnection settings
|
||||
enabledTransports: ['ws', 'wss'],
|
||||
enabledTransports: @json($pusherForceWs ? ['ws'] : ['ws', 'wss']),
|
||||
disabledTransports: ['sockjs', 'xhr_streaming', 'xhr_polling'],
|
||||
// Attempt to reconnect on connection lost
|
||||
autoReconnect: true,
|
||||
|
||||
@@ -67,21 +67,51 @@
|
||||
<span x-show="popups.realtime === true">
|
||||
@if (!isCloud())
|
||||
<x-popup>
|
||||
<x-slot:title>
|
||||
<span class="font-bold text-left text-red-500">WARNING: </span> Cannot connect to real-time service
|
||||
</x-slot:title>
|
||||
<x-slot:description>
|
||||
<div>This will cause unusual problems on the
|
||||
UI! <br><br>
|
||||
Please ensure that you have opened the
|
||||
<a class="underline" href='https://coolify.io/docs/knowledge-base/server/firewall'
|
||||
target='_blank'>required ports</a> or get
|
||||
help on <a class="underline" href='https://coollabs.io/discord' target='_blank'>Discord</a>.
|
||||
<x-slot:customActions>
|
||||
<div
|
||||
class="relative mx-auto flex w-full max-w-2xl flex-col gap-5 overflow-hidden rounded-2xl border border-red-200 bg-white p-5 shadow-modal sm:p-6 dark:border-red-500/20 dark:bg-surface">
|
||||
<button type="button" aria-label="Dismiss real-time connection warning"
|
||||
class="absolute top-3 right-3 flex size-7 items-center justify-center rounded-full 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"
|
||||
@click="bannerVisible=false;disableRealtime()">
|
||||
<x-reicon name="x" class="size-3.5" />
|
||||
</button>
|
||||
|
||||
<div class="flex items-start gap-4 pr-8">
|
||||
<div
|
||||
class="hidden size-12 shrink-0 items-center justify-center rounded-xl border border-red-200 bg-red-50 text-red-600 sm:flex dark:border-red-500/20 dark:bg-red-500/10 dark:text-red-400">
|
||||
<x-reicon name="alert-triangle" class="size-6" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h2 class="text-[15px]! leading-5! font-semibold! text-black dark:text-fg">
|
||||
Cannot connect to real-time service
|
||||
</h2>
|
||||
<p class="mt-1 text-[12px] leading-5 text-neutral-500 dark:text-fg-dim">
|
||||
This will cause unusual problems on the UI. Open the
|
||||
<a class="font-medium text-coollabs underline decoration-coollabs/30 underline-offset-2 transition-colors hover:text-coollabs-100 dark:text-warning dark:decoration-warning/30 dark:hover:text-warning/90"
|
||||
href="https://coolify.io/docs/knowledge-base/server/firewall"
|
||||
target="_blank" rel="noopener noreferrer">required ports</a>
|
||||
or get help on
|
||||
<a class="font-medium text-coollabs underline decoration-coollabs/30 underline-offset-2 transition-colors hover:text-coollabs-100 dark:text-warning dark:decoration-warning/30 dark:hover:text-warning/90"
|
||||
href="https://coollabs.io/discord" target="_blank"
|
||||
rel="noopener noreferrer">Discord</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-end">
|
||||
<a target="_blank" rel="noopener noreferrer"
|
||||
href="https://coolify.io/docs/knowledge-base/server/firewall"
|
||||
class="button h-9 justify-center sm:min-w-28">
|
||||
View docs
|
||||
</a>
|
||||
<button type="button"
|
||||
class="button h-9 justify-center bg-red-600! text-white! ring-1 ring-red-600/25 hover:bg-red-700! sm:min-w-40 dark:bg-red-500! dark:ring-red-500/30 dark:hover:bg-red-400!"
|
||||
@click="bannerVisible=false;disableRealtime()">
|
||||
Acknowledge & disable
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</x-slot:description>
|
||||
<x-slot:button-text @click="disableRealtime()">
|
||||
Acknowledge & Disable This Popup
|
||||
</x-slot:button-text>
|
||||
</x-slot:customActions>
|
||||
</x-popup>
|
||||
@endif
|
||||
</span>
|
||||
@@ -192,28 +222,47 @@
|
||||
@if (!currentTeam()->isAnyNotificationEnabled())
|
||||
<span x-show="popups.notification">
|
||||
<x-popup>
|
||||
<x-slot:title>
|
||||
No notifications enabled.
|
||||
</x-slot:title>
|
||||
<x-slot:icon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="text-red-500 stroke-current w-14 h-14 shrink-0"
|
||||
fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</x-slot:icon>
|
||||
<x-slot:description>
|
||||
It is
|
||||
highly recommended to enable at least
|
||||
one
|
||||
notification channel to receive important alerts.<br>Visit <a
|
||||
href="{{ route('notifications.email') }}" {{ wireNavigate() }} class="underline dark:text-white">/notification</a> to
|
||||
enable notifications.</span>
|
||||
</x-slot:description>
|
||||
<x-slot:button-text @click="disableNotification()">
|
||||
Accept and Close
|
||||
</x-slot:button-text>
|
||||
</x-popup>
|
||||
<x-slot:customActions>
|
||||
<div
|
||||
class="relative mx-auto flex w-full max-w-2xl flex-col gap-5 overflow-hidden rounded-2xl border border-neutral-200 bg-white p-5 shadow-modal sm:p-6 dark:border-white/[0.1] dark:bg-surface">
|
||||
<button type="button" aria-label="Dismiss notifications reminder"
|
||||
class="absolute top-3 right-3 flex size-7 items-center justify-center rounded-full 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"
|
||||
@click="bannerVisible=false;disableNotification()">
|
||||
<x-reicon name="x" class="size-3.5" />
|
||||
</button>
|
||||
|
||||
<div class="flex items-start gap-4 pr-8">
|
||||
<div
|
||||
class="hidden size-12 shrink-0 items-center justify-center rounded-xl border border-amber-200 bg-amber-50 text-amber-700 sm:flex dark:border-warning/20 dark:bg-warning/10 dark:text-warning">
|
||||
<x-reicon name="alert-triangle" class="size-6" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h2 class="text-[15px]! leading-5! font-semibold! text-black dark:text-fg">
|
||||
No notifications enabled
|
||||
</h2>
|
||||
<p class="mt-1 text-[12px] leading-5 text-neutral-500 dark:text-fg-dim">
|
||||
Enable at least one notification channel so you receive important alerts.
|
||||
Visit
|
||||
<a href="{{ route('notifications.email') }}" {{ wireNavigate() }}
|
||||
class="font-medium text-coollabs underline decoration-coollabs/30 underline-offset-2 transition-colors hover:text-coollabs-100 dark:text-warning dark:decoration-warning/30 dark:hover:text-warning/90">notifications</a>
|
||||
to get started.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-end">
|
||||
<a href="{{ route('notifications.email') }}" {{ wireNavigate() }}
|
||||
class="button h-9 justify-center sm:min-w-28">
|
||||
Open notifications
|
||||
</a>
|
||||
<button type="button" class="button h-9 justify-center sm:min-w-32"
|
||||
@click="bannerVisible=false;disableNotification()">
|
||||
Accept and close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</x-slot:customActions>
|
||||
</x-popup>
|
||||
</span>
|
||||
@endif
|
||||
<script>
|
||||
|
||||
@@ -12,10 +12,18 @@
|
||||
<link rel="icon" href="{{ asset('coolify-logo.svg') }}" type="image/svg+xml" />
|
||||
@endenv
|
||||
@auth
|
||||
@php
|
||||
$pusherForceWs = (bool) config('constants.pusher.force_ws');
|
||||
@endphp
|
||||
<script type="text/javascript" src="{{ URL::asset('js/echo.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ URL::asset('js/pusher.js') }}"></script>
|
||||
<script>
|
||||
window.Pusher = Pusher;
|
||||
@if ($pusherForceWs)
|
||||
if (window.Pusher && window.Pusher.Runtime) {
|
||||
window.Pusher.Runtime.getProtocol = function () { return 'http:'; };
|
||||
}
|
||||
@endif
|
||||
const EchoConstructor = typeof Echo === 'function' ? Echo : Echo.default;
|
||||
window.Echo = new EchoConstructor({
|
||||
broadcaster: 'pusher',
|
||||
@@ -25,10 +33,10 @@
|
||||
wsPort: "{{ getRealtime() }}",
|
||||
wssPort: "{{ getRealtime() }}",
|
||||
forceTLS: false,
|
||||
encrypted: true,
|
||||
encrypted: @json($pusherForceWs ? false : true),
|
||||
enableStats: false,
|
||||
enableLogging: @json(app()->environment('local')),
|
||||
enabledTransports: ['ws', 'wss'],
|
||||
enabledTransports: @json($pusherForceWs ? ['ws'] : ['ws', 'wss']),
|
||||
disabledTransports: ['sockjs', 'xhr_streaming', 'xhr_polling'],
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Layout popups (realtime warning, sponsorship, notifications) use the redesigned UI shell.
|
||||
*/
|
||||
test('realtime connection warning uses the redesigned popup shell', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/layout-popups.blade.php'));
|
||||
|
||||
expect($view)
|
||||
->toContain('Cannot connect to real-time service')
|
||||
->toContain('Acknowledge & disable')
|
||||
->toContain('customActions')
|
||||
->toContain('rounded-2xl border border-red-200')
|
||||
->toContain('name="alert-triangle"')
|
||||
->not->toContain('WARNING: </span> Cannot connect to real-time service')
|
||||
->not->toContain('Acknowledge & Disable This Popup');
|
||||
});
|
||||
|
||||
test('notification reminder uses the redesigned popup shell', function () {
|
||||
$view = file_get_contents(resource_path('views/livewire/layout-popups.blade.php'));
|
||||
|
||||
expect($view)
|
||||
->toContain('No notifications enabled')
|
||||
->toContain('Accept and close')
|
||||
->toContain('Open notifications')
|
||||
->not->toContain('Accept and Close');
|
||||
});
|
||||
@@ -107,6 +107,30 @@ it('registers the eye-off2 reicon used when password value is visible', function
|
||||
->toContain('M2.53033 1.46967');
|
||||
});
|
||||
|
||||
it('uses the same eye-off2 icon in configuration changes expand toggle', function () {
|
||||
$html = view('components.deployment.configuration-diff', [
|
||||
'diff' => [
|
||||
'changes' => [
|
||||
[
|
||||
'key' => 'env.SECRET',
|
||||
'section_label' => 'Environment',
|
||||
'label' => 'SECRET',
|
||||
'expandable' => true,
|
||||
'old_display_value' => 'old-***',
|
||||
'new_display_value' => 'new-***',
|
||||
'old_full_value' => 'old-secret-value',
|
||||
'new_full_value' => 'new-secret-value',
|
||||
],
|
||||
],
|
||||
],
|
||||
])->render();
|
||||
|
||||
// eye-off2 path (password inputs); not the older eye-off glyph.
|
||||
expect($html)
|
||||
->toContain('M2.53033 1.46967')
|
||||
->not->toContain('M22.2954 6.31083');
|
||||
});
|
||||
|
||||
it('renders env var password input before visibility toggle in tab order', function () {
|
||||
$html = Blade::render('<x-forms.env-var-input type="password" id="secret" />');
|
||||
|
||||
|
||||
@@ -38,9 +38,19 @@ it('renders copy fields as visible readonly controls with an accessible copy act
|
||||
expect($html)
|
||||
->toContain('label class="flex gap-1 items-center mb-1 text-sm font-medium text-black dark:text-white"')
|
||||
->toContain('readonly')
|
||||
->toContain('class="input pr-11 bg-white dark:bg-coolgray-100 dark:read-only:bg-coolgray-100 dark:read-only:text-white"')
|
||||
->toContain('input-with-copy-button')
|
||||
->toContain('copy-button')
|
||||
->toContain('aria-label="Copy to clipboard"')
|
||||
->toContain('title="Copy to clipboard"')
|
||||
->toContain('rounded-sm p-1.5 text-neutral-500 transition-colors hover:text-neutral-700 focus-visible:ring-2 focus-visible:ring-coollabs focus-visible:ring-offset-2 dark:text-neutral-400 dark:hover:text-white dark:focus-visible:ring-warning dark:focus-visible:ring-offset-base')
|
||||
->toContain('class="w-5 h-5 text-green-500"');
|
||||
->toContain('class="size-[18px] text-green-500"');
|
||||
});
|
||||
|
||||
it('keeps copy button padding above settings-workspace input overrides', function () {
|
||||
$css = file_get_contents(resource_path('css/app.css'));
|
||||
|
||||
expect($css)
|
||||
->toContain('.input.input-with-copy-button')
|
||||
->toContain('.application-settings-workspace .input.input-with-copy-button')
|
||||
->toContain('.application-settings-form .input.input-with-copy-button')
|
||||
->toContain('padding-right: 2.5rem');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user