fix(ui): re-sync active server on bfcache restore of a details page

wire:navigate restores Back/Forward pages from its client cache without re-running
Show::mount(), which could leave active_server_id pointing at a different server
than the details page on screen. Add a servers.activate endpoint + a global
livewire:navigated hook that re-asserts the on-screen server on every navigation
(including cache restores).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/v1-foundation
boban 2026-06-14 19:29:24 +02:00
parent 784566d06b
commit 6e0ec724ca
4 changed files with 34 additions and 1 deletions

View File

@ -198,3 +198,17 @@ document.addEventListener('alpine:init', () => {
},
}));
});
// Keep the active-server session in sync with the server-details page on screen, even
// when wire:navigate restores a page from its bfcache (Back/Forward) without re-running
// Show::mount(). The details page tags its root with data-clusev-activate=<activate URL>.
document.addEventListener('livewire:navigated', () => {
const el = document.querySelector('[data-clusev-activate]');
if (!el) return;
const token = document.querySelector('meta[name="csrf-token"]')?.content;
fetch(el.dataset.clusevActivate, {
method: 'POST',
headers: { 'X-CSRF-TOKEN': token ?? '', 'X-Requested-With': 'XMLHttpRequest' },
keepalive: true,
}).catch(() => {});
});

View File

@ -36,7 +36,7 @@
];
@endphp
<div class="space-y-5" wire:poll.10s="pollMetrics" wire:init="load">
<div class="space-y-5" wire:poll.10s="pollMetrics" wire:init="load" data-clusev-activate="{{ route('servers.activate', $server) }}">
<a href="{{ route('servers.index') }}"
class="inline-flex min-h-11 items-center gap-1.5 font-mono text-[11px] uppercase tracking-[0.2em] text-ink-3 hover:text-ink-2">
<x-icon name="switcher" class="h-3.5 w-3.5 rotate-90" />

View File

@ -11,6 +11,7 @@ use App\Livewire\Services;
use App\Livewire\Settings;
use App\Livewire\System;
use App\Livewire\Versions;
use App\Models\Server;
use App\Services\DeploymentService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth as AuthFacade;
@ -80,6 +81,14 @@ Route::middleware('auth')->group(function () {
Route::get('/servers', Servers\Index::class)->name('servers.index');
Route::get('/servers/{server}', Servers\Show::class)->name('servers.show');
// Re-assert the active server when a details page is shown — incl. a wire:navigate
// bfcache restore (Back/Forward), which never re-runs Show::mount(). A tiny JS hook
// on `livewire:navigated` posts here so the session context never drifts.
Route::post('/servers/{server}/activate', function (Server $server) {
session(['active_server_id' => $server->id]);
return response()->noContent();
})->name('servers.activate');
Route::get('/services', Services\Index::class)->name('services.index');
Route::get('/files', Files\Index::class)->name('files.index');

View File

@ -19,6 +19,16 @@ class ServerContextSyncTest extends TestCase
return Server::create(['name' => $name, 'ip' => '10.0.0.'.rand(2, 250), 'ssh_port' => 22, 'status' => 'online']);
}
public function test_activate_endpoint_sets_active_server(): void
{
$this->actingAs(User::factory()->create());
$b = $this->server('B');
$this->post(route('servers.activate', $b))->assertNoContent();
$this->assertSame($b->id, session('active_server_id'));
}
public function test_viewing_server_details_sets_it_active(): void
{
$this->actingAs(User::factory()->create());