feat(portal): bind the cloud card to the real instance (plan/seats/domain/quota) so the maintenance badge matches it

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

View File

@ -18,28 +18,39 @@ class Cloud extends Component
$locale = app()->getLocale();
$growth = [180, 188, 195, 199, 204, 210, 214, 219, 223, 228, 231, 235];
// 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.
// The card is built from the customer's real service instance, so the
// maintenance badge below can be scoped to exactly that instance's host.
$customer = $this->customer();
$shown = $customer?->instances()
->whereIn('status', ['active', 'provisioning', 'cancellation_scheduled'])
->latest('id')->first();
$maintenance = MaintenanceWindow::forInstance($shown)->first();
$plans = (array) config('provisioning.plans');
$planKey = $shown->plan ?? 'team';
$plan = $plans[$planKey] ?? [];
$quota = (int) ($shown->quota_gb ?? ($plan['quota_gb'] ?? 500));
// Usage metering is not wired yet — show the fixture curve until it is.
$used = min($growth[count($growth) - 1], $quota);
$domain = $shown?->custom_domain
?: ($shown?->subdomain ? $shown->subdomain.'.'.config('provisioning.dns.zone', 'clupilot.com') : 'cloud.example.com');
return view('livewire.cloud', [
'instance' => [
'name' => 'Cloud der Kanzlei Berger',
'domain' => 'cloud.kanzlei-berger.at',
'status' => 'active',
'plan' => __('cloud.plan_line', ['plan' => 'CluPilot Cloud Team', 'price' => 179]),
'name' => $customer ? __('cloud.instance_name', ['name' => $customer->name]) : __('cloud.title'),
'domain' => $domain,
'status' => $shown->status ?? 'active',
'plan' => __('cloud.plan_line', [
'plan' => 'CluPilot Cloud '.__('billing.plan.'.$planKey),
'price' => (int) round(($plan['price_cents'] ?? 0) / 100),
]),
'location' => __('cloud.datacenter'),
'version' => 'Nextcloud 31.0.4',
'php' => 'PHP 8.3 · MariaDB 11.4',
'storageUsed' => 235,
'storageQuota' => 500,
'seats' => __('billing.seats_count', ['count' => 25]),
'performance' => __('billing.perf.enhanced'),
'storageUsed' => $used,
'storageQuota' => $quota,
'seats' => __('billing.seats_count', ['count' => $plan['seats'] ?? 0]),
'performance' => __('billing.perf.'.($plan['performance'] ?? 'standard')),
],
'storageChart' => [
'type' => 'line',
@ -64,7 +75,7 @@ class Cloud extends Component
'plugins' => ['legend' => ['display' => false]],
],
],
'storageLabel' => Number::format(235, locale: $locale).' / '.Number::format(500, locale: $locale).' GB',
'storageLabel' => Number::format($used, locale: $locale).' / '.Number::format($quota, locale: $locale).' GB',
'maintenance' => $maintenance,
]);
}

View File

@ -14,6 +14,7 @@ return [
'current' => 'aktuell',
'runtime' => 'Laufzeit',
'seats' => 'Benutzer',
'instance_name' => 'Cloud der :name',
'maintenance_planned' => 'Wartung geplant',
'maintenance_active' => 'Wartung läuft',
'maintenance_window' => 'Zeitraum: :start bis :end.',

View File

@ -14,6 +14,7 @@ return [
'current' => 'up to date',
'runtime' => 'Runtime',
'seats' => 'Users',
'instance_name' => ':name cloud',
'maintenance_planned' => 'Maintenance planned',
'maintenance_active' => 'Maintenance in progress',
'maintenance_window' => 'Window: :start to :end.',

View File

@ -15,3 +15,19 @@ it('renders portal tabs for authenticated users', function (string $tab) {
->assertOk()
->assertSee('CluPilot');
})->with($tabs);
it('binds the cloud card to the real instance', function () {
$user = \App\Models\User::factory()->create(['email' => 'c@cloud.test']);
$customer = \App\Models\Customer::factory()->create(['email' => 'c@cloud.test', 'user_id' => $user->id, 'name' => 'Acme AG']);
$host = \App\Models\Host::factory()->active()->create();
\App\Models\Instance::factory()->create([
'customer_id' => $customer->id, 'host_id' => $host->id, 'status' => 'active',
'plan' => 'business', 'subdomain' => 'acme-ag', 'quota_gb' => 1000,
]);
$this->actingAs($user)->get(route('cloud'))
->assertOk()
->assertSee('acme-ag.'.config('provisioning.dns.zone')) // real subdomain, not a fixture
->assertSee(__('billing.plan.business'))
->assertSee(__('billing.perf.high'));
});