diff --git a/app/Livewire/Cloud.php b/app/Livewire/Cloud.php index 1a23b70..296fb9e 100644 --- a/app/Livewire/Cloud.php +++ b/app/Livewire/Cloud.php @@ -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' => [ diff --git a/app/Models/MaintenanceWindow.php b/app/Models/MaintenanceWindow.php index d1b7bd6..c429ec5 100644 --- a/app/Models/MaintenanceWindow.php +++ b/app/Models/MaintenanceWindow.php @@ -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 diff --git a/tests/Feature/Admin/MaintenanceTest.php b/tests/Feature/Admin/MaintenanceTest.php index a9c97f7..e5e5d7e 100644 --- a/tests/Feature/Admin/MaintenanceTest.php +++ b/tests/Feature/Admin/MaintenanceTest.php @@ -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')