From 861a9049920cf227ef3dd8bbf3e308850225833c Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:53:40 +0200 Subject: [PATCH] fix(server): surface pending proxy and outdated Traefik states --- app/Jobs/CheckTraefikVersionForServerJob.php | 8 +- app/Livewire/ActivityMonitor.php | 15 +++- app/Livewire/Server/Navbar.php | 19 +++- app/Livewire/Server/Proxy.php | 6 +- app/Models/Server.php | 27 ++++++ resources/css/utilities.css | 3 +- .../proxy-configuration-warning.blade.php | 37 ++++++++ .../views/components/server/sidebar.blade.php | 20 ++++- .../views/livewire/server/index.blade.php | 39 +++++--- .../views/livewire/server/navbar.blade.php | 16 +++- .../views/livewire/server/proxy.blade.php | 15 +++- .../Feature/ActivityMonitorCrossTeamTest.php | 20 +++++ tests/Feature/ButtonHeightConsistencyTest.php | 4 +- .../DashboardServerMetricsChartTest.php | 19 ++++ tests/Feature/Proxy/RestartProxyTest.php | 59 ++++++++++++ .../ProxyConfigurationLoadingStateTest.php | 12 +++ tests/Feature/ServerSidebarIconsTest.php | 14 +++ tests/Feature/TraefikVersionStateTest.php | 89 +++++++++++++++++++ 18 files changed, 398 insertions(+), 24 deletions(-) create mode 100644 resources/views/components/proxy-configuration-warning.blade.php create mode 100644 tests/Feature/ProxyConfigurationLoadingStateTest.php create mode 100644 tests/Feature/TraefikVersionStateTest.php diff --git a/app/Jobs/CheckTraefikVersionForServerJob.php b/app/Jobs/CheckTraefikVersionForServerJob.php index 91869eb12d..054a739bc6 100644 --- a/app/Jobs/CheckTraefikVersionForServerJob.php +++ b/app/Jobs/CheckTraefikVersionForServerJob.php @@ -33,10 +33,11 @@ class CheckTraefikVersionForServerJob implements ShouldBeEncrypted, ShouldQueue */ public function handle(): void { + $this->clearOutdatedInfo(); + // Detect current version (makes SSH call) $currentVersion = getTraefikVersionFromDockerCompose($this->server); - // Update detected version in database $this->server->update(['detected_traefik_version' => $currentVersion]); if (! $currentVersion) { @@ -113,6 +114,11 @@ class CheckTraefikVersionForServerJob implements ShouldBeEncrypted, ShouldQueue ProxyStatusChangedUI::dispatch($this->server->team_id); } + private function clearOutdatedInfo(): void + { + $this->server->update(['traefik_outdated_info' => null]); + } + /** * Get information about newer branches if available. */ diff --git a/app/Livewire/ActivityMonitor.php b/app/Livewire/ActivityMonitor.php index 665d14ba0e..25935d88e2 100644 --- a/app/Livewire/ActivityMonitor.php +++ b/app/Livewire/ActivityMonitor.php @@ -29,7 +29,10 @@ class ActivityMonitor extends Component public static $eventDispatched = false; - protected $listeners = ['activityMonitor' => 'newMonitorActivity']; + protected $listeners = [ + 'activityMonitor' => 'newMonitorActivity', + 'processDialogClosed' => 'clearActivity', + ]; public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished', $eventData = null, $header = null) { @@ -50,6 +53,16 @@ class ActivityMonitor extends Component $this->isPollingActive = true; } + public function clearActivity(): void + { + $this->activityId = null; + $this->activity = null; + $this->isPollingActive = false; + $this->eventToDispatch = 'activityFinished'; + $this->eventData = null; + self::$eventDispatched = false; + } + public function hydrateActivity() { if ($this->activityId === null) { diff --git a/app/Livewire/Server/Navbar.php b/app/Livewire/Server/Navbar.php index 31a8578657..d9f70ea253 100644 --- a/app/Livewire/Server/Navbar.php +++ b/app/Livewire/Server/Navbar.php @@ -163,6 +163,7 @@ class Navbar extends Component $previousStatus = $this->proxyStatus; $this->server->refresh(); $this->proxyStatus = $this->server->proxy->status ?? 'unknown'; + $this->dispatchProxyConfigurationState(); // If event contains activityId, open activity monitor if ($event && isset($event['activityId'])) { @@ -227,6 +228,16 @@ class Navbar extends Component { $this->server->refresh(); $this->server->load('settings'); + $this->dispatchProxyConfigurationState(); + } + + private function dispatchProxyConfigurationState(): void + { + $this->dispatch( + 'proxy-configuration-state-changed', + pending: $this->server->hasPendingProxyConfiguration(), + traefikOutdated: $this->server->hasCurrentTraefikOutdatedInfo(), + ); } public function refreshSentinelStatus($event = null): void @@ -248,10 +259,12 @@ class Navbar extends Component return false; } - // Check if server has outdated info stored - $outdatedInfo = $this->server->traefik_outdated_info; + return $this->server->hasCurrentTraefikOutdatedInfo(); + } - return ! empty($outdatedInfo) && isset($outdatedInfo['type']); + public function getHasPendingProxyConfigurationProperty(): bool + { + return $this->server->hasPendingProxyConfiguration(); } public function render() diff --git a/app/Livewire/Server/Proxy.php b/app/Livewire/Server/Proxy.php index 8cd4e96405..811a01eb19 100644 --- a/app/Livewire/Server/Proxy.php +++ b/app/Livewire/Server/Proxy.php @@ -161,6 +161,7 @@ class Proxy extends Component $this->server->proxy->redirect_url = $this->redirectUrl; $this->server->save(); $this->server->setupDefaultRedirect(); + $this->dispatch('refreshServerShow'); $this->dispatch('success', 'Proxy configuration saved.'); } catch (\Throwable $e) { return handleError($e, $this); @@ -175,6 +176,7 @@ class Proxy extends Component $this->proxySettings = GetProxyConfiguration::run($this->server, forceRegenerate: true); SaveProxyConfiguration::run($this->server, $this->proxySettings); $this->server->save(); + $this->dispatch('refreshServerShow'); $this->dispatch('success', 'Proxy configuration reset to default.'); } catch (\Throwable $e) { return handleError($e, $this); @@ -276,7 +278,9 @@ class Proxy extends Component // Check if we have outdated info stored for this server (faster than computing) $outdatedInfo = $this->server->traefik_outdated_info; - if ($outdatedInfo && isset($outdatedInfo['type']) && $outdatedInfo['type'] === 'minor_upgrade') { + $storedCurrentVersion = ltrim((string) data_get($outdatedInfo, 'current'), 'v'); + $detectedCurrentVersion = ltrim($currentVersion, 'v'); + if ($storedCurrentVersion === $detectedCurrentVersion && data_get($outdatedInfo, 'type') === 'minor_upgrade') { // Use the upgrade_target field if available (e.g., "v3.6") if (isset($outdatedInfo['upgrade_target'])) { return str_starts_with($outdatedInfo['upgrade_target'], 'v') diff --git a/app/Models/Server.php b/app/Models/Server.php index b5b11d26a5..f7a4bf20c0 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -858,6 +858,33 @@ $siteAddress { return data_get($this->proxy, 'type'); } + public function hasPendingProxyConfiguration(): bool + { + if ($this->proxy->get('status') !== 'running') { + return false; + } + + $savedSettings = $this->proxy->get('last_saved_settings'); + $appliedSettings = $this->proxy->get('last_applied_settings'); + + return filled($savedSettings) && filled($appliedSettings) && $savedSettings !== $appliedSettings; + } + + public function hasCurrentTraefikOutdatedInfo(): bool + { + if ($this->proxyType() !== ProxyTypes::TRAEFIK->value) { + return false; + } + + $detectedVersion = ltrim((string) $this->detected_traefik_version, 'v'); + $storedVersion = ltrim((string) data_get($this->traefik_outdated_info, 'current'), 'v'); + $type = data_get($this->traefik_outdated_info, 'type'); + + return filled($detectedVersion) + && $storedVersion === $detectedVersion + && in_array($type, ['patch_update', 'minor_upgrade'], true); + } + public function scopeWithProxy(): Builder { return $this->proxy->modelScope(); diff --git a/resources/css/utilities.css b/resources/css/utilities.css index 7c690cba75..6fdd260b5f 100644 --- a/resources/css/utilities.css +++ b/resources/css/utilities.css @@ -126,8 +126,7 @@ } @utility button { - /* h-9 matches input-select; nowrap + shrink-0 keep side-by-side action rows equal height */ - @apply inline-flex shrink-0 gap-1.5 justify-center items-center whitespace-nowrap px-2.5 h-9 min-h-9 text-[13px] text-black normal-case rounded-md border outline-0 cursor-pointer font-medium transition-colors bg-white border-neutral-200 hover:bg-neutral-100 dark:bg-white/[0.06] dark:text-fg dark:hover:text-fg dark:hover:bg-white/[0.1] dark:border-white/[0.08] hover:text-black disabled:cursor-not-allowed min-w-fit dark:disabled:text-fg-faint disabled:border-neutral-200 dark:disabled:border-white/[0.06] disabled:hover:bg-transparent disabled:bg-transparent disabled:text-neutral-300 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent; + @apply inline-flex shrink-0 gap-1.5 justify-center items-center whitespace-nowrap px-2.5 h-8 min-h-8 text-[13px] text-black normal-case rounded-md border outline-0 cursor-pointer font-medium transition-colors bg-white border-neutral-200 hover:bg-neutral-100 dark:bg-white/[0.06] dark:text-fg dark:hover:text-fg dark:hover:bg-white/[0.1] dark:border-white/[0.08] hover:text-black disabled:cursor-not-allowed min-w-fit dark:disabled:text-fg-faint disabled:border-neutral-200 dark:disabled:border-white/[0.06] disabled:hover:bg-transparent disabled:bg-transparent disabled:text-neutral-300 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent; } @utility button-highlighted { diff --git a/resources/views/components/proxy-configuration-warning.blade.php b/resources/views/components/proxy-configuration-warning.blade.php new file mode 100644 index 0000000000..7337779d0e --- /dev/null +++ b/resources/views/components/proxy-configuration-warning.blade.php @@ -0,0 +1,37 @@ +@props(['canRestart' => false]) + +
+ + + +
diff --git a/resources/views/components/server/sidebar.blade.php b/resources/views/components/server/sidebar.blade.php index 006efb2cac..41cbd7ce9d 100644 --- a/resources/views/components/server/sidebar.blade.php +++ b/resources/views/components/server/sidebar.blade.php @@ -55,6 +55,8 @@ 'icon' => 'network', 'group' => 'Platform', 'visible' => ! $server->isSwarmWorker() && ! $server->settings->is_build_server, + 'warning' => $server->hasCurrentTraefikOutdatedInfo(), + 'tracks_proxy_configuration' => true, 'children' => [ ['label' => 'Configuration', 'route' => 'server.proxy', 'active' => $activeSubMenu === 'configuration', 'icon' => 'settings'], ['label' => 'Dynamic Configurations', 'route' => 'server.proxy.dynamic-confs', 'active' => $activeSubMenu === 'dynamic-confs', 'icon' => 'sliders', 'visible' => $server->proxySet()], @@ -167,7 +169,15 @@ $groupedServerMenuItems = $serverMenuItems->groupBy('group'); @endphp -