feat(ui): dock configuration warnings in the navigation HUD

This commit is contained in:
Andras Bacsai committed 2026-08-12 16:24:01 +02:00
1 parent feb5be3a3c
commit 8e6e2b099a
12 files changed
+130 -45

No files matched your search

@@ -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,
+3 -1
View File
@@ -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"