fix(portal): scope the per-instance maintenance badge to that instance's host

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/portal-design
nexxo 2026-07-25 19:12:33 +02:00
parent a321d963d7
commit ac3b3a4e11
3 changed files with 43 additions and 3 deletions

View File

@ -18,10 +18,14 @@ class Cloud extends Component
$locale = app()->getLocale();
$growth = [180, 188, 195, 199, 204, 210, 214, 219, 223, 228, 231, 235];
// Maintenance affecting THIS customer's instance host — shown as a badge
// on the instance itself, not just as a global banner.
// Maintenance affecting THE DISPLAYED instance's host — scoped to that one
// instance so a customer with several instances never sees another
// instance's window on this card.
$customer = $this->customer();
$maintenance = $customer ? MaintenanceWindow::bannerFor($customer)->first() : null;
$shown = $customer?->instances()
->whereIn('status', ['active', 'provisioning', 'cancellation_scheduled'])
->latest('id')->first();
$maintenance = MaintenanceWindow::forInstance($shown)->first();
return view('livewire.cloud', [
'instance' => [

View File

@ -82,6 +82,26 @@ class MaintenanceWindow extends Model
->get();
}
/**
* Scheduled windows within the display horizon that affect ONE instance's
* host for a per-instance badge, so a customer with several instances
* never sees another instance's maintenance on this card.
*/
public static function forInstance(?Instance $instance): Collection
{
if ($instance === null || $instance->host_id === null) {
return collect();
}
return self::query()
->where('state', 'scheduled')
->where('ends_at', '>=', now())
->where('starts_at', '<=', now()->addHours(self::DISPLAY_HOURS))
->whereHas('hosts', fn (Builder $q) => $q->whereKey($instance->host_id))
->orderBy('starts_at')
->get();
}
/**
* Scheduled windows to show a given customer right now: within the display
* horizon (72 h before start until the end) and touching one of the

View File

@ -65,6 +65,22 @@ it('emails a cancellation to customers who were announced', function () {
Mail::assertQueued(\App\Mail\MaintenanceCancelledMail::class, 1);
});
it('scopes the per-instance badge to that instance host', function () {
$hostA = Host::factory()->active()->create();
$hostB = Host::factory()->active()->create();
$customer = Customer::factory()->create();
$instA = Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $hostA->id, 'status' => 'active']);
$instB = Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $hostB->id, 'status' => 'active']);
// A window on host A only.
$window = MaintenanceWindow::factory()->scheduled()->create();
$window->hosts()->attach($hostA->id);
expect(MaintenanceWindow::forInstance($instA))->toHaveCount(1)
->and(MaintenanceWindow::forInstance($instB))->toHaveCount(0) // not another instance's window
->and(MaintenanceWindow::bannerFor($customer))->toHaveCount(1); // customer-level banner still shows
});
it('requires a host to publish', function () {
Livewire::actingAs(operator('Owner'))->test(Maintenance::class)
->set('title', 'X')