mirror of
https://github.com/tiennm99/coolify.git
synced 2026-09-02 12:19:57 +00:00
feat(ui): dock configuration warnings in the navigation HUD
This commit is contained in:
@@ -13,7 +13,8 @@
|
||||
$linkItemClasses = 'listbox-option justify-start! gap-2.5!';
|
||||
@endphp
|
||||
|
||||
<div @class(['relative', 'w-full' => $fullWidth]) x-data="{ open: false }" @keydown.escape.window="open = false">
|
||||
<div @class(['relative', 'w-full' => $fullWidth]) x-data="{ open: false }"
|
||||
x-effect="$dispatch('resource-actions-toggled', { open })" @keydown.escape.window="open = false">
|
||||
<button type="button" @click="open = !open" @click.outside="open = false" title="Open application links"
|
||||
@class([
|
||||
'app-tab shrink-0 gap-1' => !$fullWidth,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
@props(['diff' => []])
|
||||
|
||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @keydown.escape.window="open = false">
|
||||
<button type="button" aria-label="Configuration changes not applied" aria-haspopup="dialog"
|
||||
:aria-expanded="open" @click="open = !open"
|
||||
class="flex h-8 items-center justify-center gap-1.5 rounded-lg px-2 text-amber-700 transition-colors hover:bg-amber-100 dark:text-warning dark:hover:bg-warning/10">
|
||||
<x-reicon name="alert-triangle" class="size-4" />
|
||||
<span class="hidden text-xs font-medium lg:inline">Changes pending</span>
|
||||
</button>
|
||||
|
||||
<div x-show="open" x-cloak x-transition.opacity role="dialog"
|
||||
class="fixed top-14 left-1/2 z-[1100] w-[calc(100vw-2rem)] max-w-sm -translate-x-1/2 rounded-lg p-3 lg:absolute lg:top-full lg:right-0 lg:left-auto lg:mt-2 lg:translate-x-0"
|
||||
style="background: var(--coollabs-elevated); box-shadow: 0 0 0 1px var(--coollabs-line), var(--shadow-modal);">
|
||||
<div class="flex items-start gap-2.5">
|
||||
<span
|
||||
class="flex size-7 shrink-0 items-center justify-center rounded-md bg-amber-100 text-amber-700 dark:bg-warning/10 dark:text-warning">
|
||||
<x-reicon name="alert-triangle" class="size-4" />
|
||||
</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-[13px] font-semibold leading-4 text-neutral-950 dark:text-fg">
|
||||
The latest configuration has not been applied
|
||||
</p>
|
||||
<p class="mt-0.5 text-[11px] leading-4 text-neutral-600 dark:text-fg-dim">
|
||||
@if (data_get($diff, 'count'))
|
||||
{{ data_get($diff, 'count') }}
|
||||
{{ data_get($diff, 'count') === 1 ? 'change' : 'changes' }} unapplied.
|
||||
{{ data_get($diff, 'requires_build') ? 'Rebuild required.' : 'Redeploy to apply.' }}
|
||||
<button type="button"
|
||||
class="ml-0.5 inline-flex items-center gap-0.5 font-semibold text-coollabs transition-colors hover:text-coollabs-100 dark:text-warning dark:hover:text-warning/80"
|
||||
@click="open = false; $dispatch('open-configuration-diff')">
|
||||
View changes
|
||||
<x-reicon name="arrow-right" class="size-2.5" />
|
||||
</button>
|
||||
@else
|
||||
Redeploy to apply.
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,7 +67,7 @@
|
||||
<template x-if="!iconOnly">
|
||||
<div class="relative flex items-start gap-2.5 rounded-lg p-3 pr-10"
|
||||
:class="compact
|
||||
? 'w-[calc(100vw-2rem)] cursor-pointer sm:w-auto sm:max-w-[calc(100vw-2rem)]'
|
||||
? 'w-[calc(100vw-2rem)] max-w-sm cursor-pointer'
|
||||
: 'w-[calc(100vw-2rem)] max-w-sm'"
|
||||
@click="restore()"
|
||||
style="background: var(--coollabs-elevated); box-shadow: 0 0 0 1px var(--coollabs-line), var(--shadow-modal);">
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
$linkItemClasses = 'listbox-option justify-start! gap-2.5!';
|
||||
@endphp
|
||||
|
||||
<div @class(['relative', 'w-full' => $fullWidth]) x-data="{ open: false }" @keydown.escape.window="open = false">
|
||||
<div @class(['relative', 'w-full' => $fullWidth]) x-data="{ open: false }"
|
||||
x-effect="$dispatch('resource-actions-toggled', { open })" @keydown.escape.window="open = false">
|
||||
<button type="button" @click="open = !open" @click.outside="open = false" title="Open service links"
|
||||
@class([
|
||||
'app-tab shrink-0 gap-1' => !$fullWidth,
|
||||
|
||||
@@ -60,13 +60,14 @@
|
||||
</div>
|
||||
{{-- Dev Server-Timing HUD docks here (local only; empty in production) --}}
|
||||
<div id="server-timing-hud-slot" data-server-timing-hud-slot class="hidden shrink-0 items-center"></div>
|
||||
<div id="configuration-warning-hud-slot" class="relative shrink-0"></div>
|
||||
{{-- Resource actions dock here on desktop. --}}
|
||||
<div id="resource-action-hud-slot" class="hidden shrink-0 items-center xl:flex"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- ============ MOBILE SLIDE-OVER SIDEBAR ============ --}}
|
||||
<div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true">
|
||||
<div class="relative z-[1000] lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true">
|
||||
<div class="fixed inset-0 bg-black/80" x-on:click="open = false"></div>
|
||||
<div class="fixed inset-y-0 right-0 flex h-full">
|
||||
<div
|
||||
@@ -111,6 +112,7 @@
|
||||
{{-- Dev Server-Timing HUD docks here on <lg (desktop uses #server-timing-hud-slot) --}}
|
||||
<div id="server-timing-hud-slot-mobile" data-server-timing-hud-slot
|
||||
class="hidden shrink-0 items-center"></div>
|
||||
<div id="configuration-warning-hud-slot-mobile" class="relative shrink-0"></div>
|
||||
<x-top-user-menu />
|
||||
<button type="button" class="-m-1 p-2 text-neutral-500 dark:text-fg-dim" x-on:click="open = !open">
|
||||
<span class="sr-only">Open sidebar</span>
|
||||
|
||||
@@ -187,6 +187,7 @@
|
||||
<x-applications.links :application="$application" />
|
||||
</div>
|
||||
<div id="application-desktop-actions" class="relative" x-data="{ open: false }"
|
||||
x-effect="$dispatch('resource-actions-toggled', { open })"
|
||||
@click.outside="open = false" @keydown.escape.window="open = false">
|
||||
<button type="button" class="button" @click="open = !open" :aria-expanded="open"
|
||||
aria-haspopup="menu">
|
||||
|
||||
@@ -140,6 +140,7 @@
|
||||
@if ($database->destination->server->isFunctional())
|
||||
@if (! $databaseStatus->startsWith('exited'))
|
||||
<div id="database-desktop-actions" class="relative" x-data="{ open: false }"
|
||||
x-effect="$dispatch('resource-actions-toggled', { open })"
|
||||
@click.outside="open = false" @keydown.escape.window="open = false">
|
||||
<button type="button" class="button" @click="open = !open" :aria-expanded="open">
|
||||
<x-reicon name="play-circle" class="size-3.5 text-warning" />
|
||||
|
||||
@@ -29,45 +29,24 @@
|
||||
|
||||
@if ($isConfigurationChanged && !is_null($resource->config_hash) && !$resource->isExited())
|
||||
@php
|
||||
$compactStoragePrefix = "configuration-warning:{$resource->uuid}:";
|
||||
$currentConfigurationHash = $resource instanceof \App\Models\Application
|
||||
? $resource->deploymentConfigurationHash()
|
||||
: md5((string) $resource->config_hash);
|
||||
$compactStorageKey = $compactStoragePrefix.$currentConfigurationHash;
|
||||
@endphp
|
||||
<div wire:key="configuration-warning-{{ $currentConfigurationHash }}"
|
||||
x-data="{ configurationDiffModalOpen: false, expandedRows: {} }">
|
||||
<x-popup-small position="top-right" :compact-after="5000" :compact-storage-key="$compactStorageKey"
|
||||
:compact-storage-prefix="$compactStoragePrefix">
|
||||
<x-slot:title>
|
||||
The latest configuration has not been applied
|
||||
</x-slot:title>
|
||||
<x-slot:icon>
|
||||
<x-reicon name="alert-triangle" class="size-4" />
|
||||
</x-slot:icon>
|
||||
<x-slot:description>
|
||||
<span>
|
||||
@if (data_get($configurationDiff, 'count'))
|
||||
{{ data_get($configurationDiff, 'count') }}
|
||||
{{ data_get($configurationDiff, 'count') === 1 ? 'change' : 'changes' }}
|
||||
unapplied.
|
||||
@if (data_get($configurationDiff, 'requires_build'))
|
||||
Rebuild required.
|
||||
@else
|
||||
Redeploy to apply.
|
||||
@endif
|
||||
<button type="button"
|
||||
class="ml-0.5 inline-flex items-center gap-0.5 font-semibold text-coollabs transition-colors hover:text-coollabs-100 dark:text-warning dark:hover:text-warning/80"
|
||||
x-on:click="configurationDiffModalOpen = true">
|
||||
View changes
|
||||
<x-reicon name="arrow-right" class="size-2.5" />
|
||||
</button>
|
||||
@else
|
||||
Redeploy to apply.
|
||||
@endif
|
||||
</span>
|
||||
</x-slot:description>
|
||||
</x-popup-small>
|
||||
x-data="{ configurationDiffModalOpen: false, expandedRows: {} }"
|
||||
@open-configuration-diff.window="configurationDiffModalOpen = true">
|
||||
@teleport('#configuration-warning-hud-slot')
|
||||
<div>
|
||||
<x-configuration-warning :diff="$configurationDiff" />
|
||||
</div>
|
||||
@endteleport
|
||||
|
||||
@teleport('#configuration-warning-hud-slot-mobile')
|
||||
<div>
|
||||
<x-configuration-warning :diff="$configurationDiff" />
|
||||
</div>
|
||||
@endteleport
|
||||
|
||||
@if (data_get($configurationDiff, 'count'))
|
||||
<template x-teleport="body">
|
||||
|
||||
@@ -287,7 +287,8 @@
|
||||
@if ($server->proxySet())
|
||||
@can('manageProxy', $server)
|
||||
<div id="server-desktop-actions" class="resource-heading-actions relative shrink-0"
|
||||
x-data="{ open: false }" @click.outside="open = false"
|
||||
x-data="{ open: false }" x-effect="$dispatch('resource-actions-toggled', { open })"
|
||||
@click.outside="open = false"
|
||||
@keydown.escape.window="open = false">
|
||||
<button type="button" class="button" @click="open = !open" :aria-expanded="open"
|
||||
aria-haspopup="menu" wire:loading.attr="disabled" wire:loading.class="is-loading"
|
||||
|
||||
@@ -102,7 +102,7 @@ it('supports timed compact popup notifications', function () {
|
||||
->not->toContain('<div x-show="!iconOnly"')
|
||||
->not->toContain(':class="iconOnly')
|
||||
->toContain('x-show="!compact"')
|
||||
->toContain("'w-[calc(100vw-2rem)] cursor-pointer sm:w-auto sm:max-w-[calc(100vw-2rem)]'");
|
||||
->toContain("'w-[calc(100vw-2rem)] max-w-sm cursor-pointer'");
|
||||
});
|
||||
|
||||
it('warns when a service has missing required environment variables', function () {
|
||||
|
||||
@@ -156,16 +156,26 @@ it('groups application lifecycle controls in an actions dropdown', function () {
|
||||
->toContain('Deploy');
|
||||
});
|
||||
|
||||
it('raises the desktop top bar while the service actions dropdown is open', function () {
|
||||
it('raises the desktop top bar while any resource dropdown is open', function () {
|
||||
$layout = file_get_contents(resource_path('views/layouts/app.blade.php'));
|
||||
$heading = file_get_contents(resource_path('views/livewire/project/service/heading.blade.php'));
|
||||
$dropdowns = [
|
||||
resource_path('views/livewire/project/application/heading.blade.php'),
|
||||
resource_path('views/livewire/project/database/heading.blade.php'),
|
||||
resource_path('views/livewire/project/service/heading.blade.php'),
|
||||
resource_path('views/livewire/server/navbar.blade.php'),
|
||||
resource_path('views/components/applications/links.blade.php'),
|
||||
resource_path('views/components/services/links.blade.php'),
|
||||
];
|
||||
|
||||
expect($layout)
|
||||
->toContain('resourceActionsOpen: false')
|
||||
->toContain("'z-[1000]': resourceActionsOpen")
|
||||
->toContain('@resource-actions-toggled.window="resourceActionsOpen = $event.detail.open"')
|
||||
->and($heading)
|
||||
->toContain("\$dispatch('resource-actions-toggled', { open })");
|
||||
->toContain('@resource-actions-toggled.window="resourceActionsOpen = $event.detail.open"');
|
||||
|
||||
foreach ($dropdowns as $dropdown) {
|
||||
expect(file_get_contents($dropdown))
|
||||
->toContain("\$dispatch('resource-actions-toggled', { open })");
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps deploy in the actions menu alongside advanced operations', function () {
|
||||
@@ -179,6 +189,28 @@ it('keeps deploy in the actions menu alongside advanced operations', function ()
|
||||
->toContain('Force deploy without cache');
|
||||
});
|
||||
|
||||
it('renders configuration warnings as navbar popovers instead of floating notifications', function () {
|
||||
$checker = file_get_contents(resource_path('views/livewire/project/shared/configuration-checker.blade.php'));
|
||||
$warning = file_get_contents(resource_path('views/components/configuration-warning.blade.php'));
|
||||
|
||||
expect($checker)
|
||||
->toContain("@teleport('#configuration-warning-hud-slot')")
|
||||
->toContain("@teleport('#configuration-warning-hud-slot-mobile')")
|
||||
->toContain('<x-configuration-warning :diff="$configurationDiff" />')
|
||||
->not->toContain('<x-popup-small position="top-right"')
|
||||
->and($warning)
|
||||
->toContain('aria-label="Configuration changes not applied"')
|
||||
->toContain('<span class="hidden text-xs font-medium lg:inline">Changes pending</span>')
|
||||
->toContain('The latest configuration has not been applied')
|
||||
->toContain('fixed top-14 left-1/2')
|
||||
->toContain('-translate-x-1/2')
|
||||
->toContain('lg:absolute lg:top-full lg:right-0 lg:left-auto')
|
||||
->toContain('lg:translate-x-0')
|
||||
->toContain('@click.outside="open = false"')
|
||||
->toContain('@keydown.escape.window="open = false"')
|
||||
->toContain("\$dispatch('open-configuration-diff')");
|
||||
});
|
||||
|
||||
it('moves application backups from the top tabs into the settings sidebar', function () {
|
||||
$heading = file_get_contents(resource_path('views/livewire/project/application/heading.blade.php'));
|
||||
$configuration = file_get_contents(resource_path('views/livewire/project/application/configuration.blade.php'));
|
||||
|
||||
@@ -63,6 +63,32 @@ it('separates the mobile sidebar from the page with a visible border', function
|
||||
expect($layout)->toContain('max-w-56 min-w-0 flex-col border-l border-neutral-200 bg-white shadow-xl dark:border-white/[0.12] dark:bg-panel');
|
||||
});
|
||||
|
||||
it('keeps the mobile navbar above floating configuration warnings', function () {
|
||||
$layout = file_get_contents(resource_path('views/layouts/app.blade.php'));
|
||||
$popup = file_get_contents(resource_path('views/components/popup-small.blade.php'));
|
||||
|
||||
expect($layout)
|
||||
->toContain('class="relative z-[1000] lg:hidden"')
|
||||
->and($popup)->toContain('z-999');
|
||||
});
|
||||
|
||||
it('keeps compact configuration warnings the same width as their expanded state', function () {
|
||||
$popup = file_get_contents(resource_path('views/components/popup-small.blade.php'));
|
||||
|
||||
expect($popup)
|
||||
->toContain("? 'w-[calc(100vw-2rem)] max-w-sm cursor-pointer'")
|
||||
->toContain(": 'w-[calc(100vw-2rem)] max-w-sm'")
|
||||
->not->toContain('sm:w-auto');
|
||||
});
|
||||
|
||||
it('provides configuration warning slots in both desktop and mobile navbars', function () {
|
||||
$layout = file_get_contents(resource_path('views/layouts/app.blade.php'));
|
||||
|
||||
expect($layout)
|
||||
->toContain('id="configuration-warning-hud-slot"')
|
||||
->toContain('id="configuration-warning-hud-slot-mobile"');
|
||||
});
|
||||
|
||||
it('shows the full team name in the header', function () {
|
||||
$layout = file_get_contents(resource_path('views/layouts/app.blade.php'));
|
||||
$switcher = file_get_contents(resource_path('views/livewire/switch-team.blade.php'));
|
||||
|
||||
Reference in New Issue
Block a user