diff --git a/app/Livewire/Admin/ConfirmDeleteDatacenter.php b/app/Livewire/Admin/ConfirmDeleteDatacenter.php new file mode 100644 index 0000000..08dd267 --- /dev/null +++ b/app/Livewire/Admin/ConfirmDeleteDatacenter.php @@ -0,0 +1,47 @@ +where('uuid', $uuid)->withCount('hosts')->firstOrFail(); + $this->uuid = $uuid; + $this->name = $dc->name; + $this->hostCount = $dc->hosts_count; + } + + public function delete() + { + $dc = Datacenter::query()->where('uuid', $this->uuid)->withCount('hosts')->first(); + + // Re-check under the current state — never delete a datacenter with hosts. + if ($dc === null || $dc->hosts_count > 0) { + return $this->redirectRoute('admin.datacenters', navigate: true); + } + + $dc->delete(); + + return $this->redirectRoute('admin.datacenters', navigate: true); + } + + public function render() + { + return view('livewire.admin.confirm-delete-datacenter'); + } +} diff --git a/app/Livewire/Admin/Datacenters.php b/app/Livewire/Admin/Datacenters.php index 7160efb..4cce056 100644 --- a/app/Livewire/Admin/Datacenters.php +++ b/app/Livewire/Admin/Datacenters.php @@ -19,6 +19,49 @@ class Datacenters extends Component #[Validate('nullable|string|max:255')] public string $location = ''; + // Inline edit state (code is immutable once created — it is referenced by hosts). + public ?string $editingUuid = null; + + public string $editName = ''; + + public string $editLocation = ''; + + public function edit(string $uuid): void + { + $dc = Datacenter::query()->where('uuid', $uuid)->first(); + if ($dc === null) { + return; + } + $this->editingUuid = $uuid; + $this->editName = $dc->name; + $this->editLocation = (string) $dc->location; + } + + public function cancelEdit(): void + { + $this->reset('editingUuid', 'editName', 'editLocation'); + } + + public function update(): void + { + if ($this->editingUuid === null) { + return; + } + + $data = $this->validate([ + 'editName' => 'required|string|max:255', + 'editLocation' => 'nullable|string|max:255', + ]); + + Datacenter::query()->where('uuid', $this->editingUuid)->update([ + 'name' => $data['editName'], + 'location' => $data['editLocation'] ?: null, + ]); + + $this->cancelEdit(); + $this->dispatch('notify', message: __('datacenters.updated')); + } + public function save(): void { // Normalize before validating so the unique check matches how the row is diff --git a/app/Livewire/Admin/Overview.php b/app/Livewire/Admin/Overview.php index 0e78be4..5dd5124 100644 --- a/app/Livewire/Admin/Overview.php +++ b/app/Livewire/Admin/Overview.php @@ -60,22 +60,13 @@ class Overview extends Component 'plugins' => ['legend' => ['display' => false]], ], ], - 'hostChart' => [ - 'type' => 'bar', - 'data' => [ - 'labels' => ['pve-fsn-1', 'pve-fsn-2', 'pve-fsn-3', 'pve-hel-1'], - 'datasets' => [[ - 'label' => '%', - 'data' => [72, 64, 81, 38], - 'backgroundColor' => ['token:accent/0.85', 'token:accent/0.85', 'token:danger/0.85', 'token:success-bright/0.85'], - 'borderRadius' => 3, - ]], - ], - 'options' => [ - 'indexAxis' => 'y', - 'scales' => ['x' => ['max' => 100, 'grid' => ['color' => 'token:border']], 'y' => ['grid' => ['display' => false]]], - 'plugins' => ['legend' => ['display' => false]], - ], + // Per-host storage load as capacity meters (a ranked comparison, not a + // trend — so meters, not a chart). level drives the meter colour. + 'hostLoad' => [ + ['name' => 'pve-fsn-1', 'pct' => 72, 'level' => 'warn'], + ['name' => 'pve-fsn-2', 'pct' => 64, 'level' => 'ok'], + ['name' => 'pve-fsn-3', 'pct' => 81, 'level' => 'high'], + ['name' => 'pve-hel-1', 'pct' => 38, 'level' => 'ok'], ], 'runs' => [ ['customer' => 'Ordination Dr. Fux', 'step' => __('admin.run.deploy'), 'state' => 'running'], diff --git a/lang/de/datacenters.php b/lang/de/datacenters.php index 3aa5d8f..09ddd2e 100644 --- a/lang/de/datacenters.php +++ b/lang/de/datacenters.php @@ -15,4 +15,15 @@ return [ 'inactive' => 'Inaktiv', 'add' => 'Rechenzentrum anlegen', 'created' => 'Rechenzentrum angelegt.', + 'updated' => 'Rechenzentrum aktualisiert.', + 'actions' => 'Aktionen', + 'edit' => 'Bearbeiten', + 'save' => 'Speichern', + 'cancel' => 'Abbrechen', + 'delete' => 'Löschen', + 'delete_title' => 'Rechenzentrum löschen?', + 'delete_body' => '„:name" wird dauerhaft entfernt. Das kann nicht rückgängig gemacht werden.', + 'delete_confirm' => 'Endgültig löschen', + 'delete_blocked_title' => 'Löschen nicht möglich', + 'delete_blocked_body' => '„:name" hat noch :count Host(s). Entferne oder verschiebe zuerst die Hosts.', ]; diff --git a/lang/en/datacenters.php b/lang/en/datacenters.php index 7fada6c..f290ec9 100644 --- a/lang/en/datacenters.php +++ b/lang/en/datacenters.php @@ -15,4 +15,15 @@ return [ 'inactive' => 'Inactive', 'add' => 'Add datacenter', 'created' => 'Datacenter created.', + 'updated' => 'Datacenter updated.', + 'actions' => 'Actions', + 'edit' => 'Edit', + 'save' => 'Save', + 'cancel' => 'Cancel', + 'delete' => 'Delete', + 'delete_title' => 'Delete datacenter?', + 'delete_body' => '“:name” will be permanently removed. This cannot be undone.', + 'delete_confirm' => 'Delete permanently', + 'delete_blocked_title' => 'Cannot delete', + 'delete_blocked_body' => '“:name” still has :count host(s). Remove or move the hosts first.', ]; diff --git a/resources/views/livewire/admin/confirm-delete-datacenter.blade.php b/resources/views/livewire/admin/confirm-delete-datacenter.blade.php new file mode 100644 index 0000000..bb7e151 --- /dev/null +++ b/resources/views/livewire/admin/confirm-delete-datacenter.blade.php @@ -0,0 +1,32 @@ +
+ @if ($hostCount > 0) +
+ + + +
+

{{ __('datacenters.delete_blocked_title') }}

+

{{ __('datacenters.delete_blocked_body', ['name' => $name, 'count' => $hostCount]) }}

+
+
+
+ {{ __('datacenters.cancel') }} +
+ @else +
+ + + +
+

{{ __('datacenters.delete_title') }}

+

{{ __('datacenters.delete_body', ['name' => $name]) }}

+
+
+
+ {{ __('datacenters.cancel') }} + + {{ __('datacenters.delete_confirm') }} + +
+ @endif +
diff --git a/resources/views/livewire/admin/datacenters.blade.php b/resources/views/livewire/admin/datacenters.blade.php index e97a244..b95aa24 100644 --- a/resources/views/livewire/admin/datacenters.blade.php +++ b/resources/views/livewire/admin/datacenters.blade.php @@ -17,24 +17,56 @@ {{ __('datacenters.name') }} {{ __('datacenters.hosts') }} {{ __('datacenters.status') }} + {{ __('datacenters.actions') }} @foreach ($datacenters as $dc) - + {{ $dc->code }} - {{ $dc->name }} - @if ($dc->location)· {{ $dc->location }}@endif - - {{ $dc->hosts_count }} - - - + @if ($editingUuid === $dc->uuid) + +
+ + +
+ @error('editName')

{{ $message }}

@enderror + + +
+ + +
+ + @else + {{ $dc->name }} + @if ($dc->location)· {{ $dc->location }}@endif + + {{ $dc->hosts_count }} + + + + +
+ + +
+ + @endif @endforeach diff --git a/resources/views/livewire/admin/overview.blade.php b/resources/views/livewire/admin/overview.blade.php index 3904319..388ee5e 100644 --- a/resources/views/livewire/admin/overview.blade.php +++ b/resources/views/livewire/admin/overview.blade.php @@ -29,17 +29,30 @@

{{ __('admin.fleet_growth_sub') }}

-
+

{{ __('admin.host_load') }}

{{ __('admin.host_load_sub') }}

-
+
-
+

{{ __('admin.mrr_trend') }}

-
+
diff --git a/tests/Feature/Admin/DatacenterTest.php b/tests/Feature/Admin/DatacenterTest.php index 3916b12..fff57ce 100644 --- a/tests/Feature/Admin/DatacenterTest.php +++ b/tests/Feature/Admin/DatacenterTest.php @@ -1,8 +1,10 @@ assertHasErrors(['code']); }); +it('edits a datacenter name and location', function () { + $dc = Datacenter::factory()->create(['code' => 'nbg', 'name' => 'Old', 'location' => 'XX']); + + Livewire::actingAs(admin()) + ->test(Datacenters::class) + ->call('edit', $dc->uuid) + ->set('editName', 'Nürnberg') + ->set('editLocation', 'DE') + ->call('update') + ->assertHasNoErrors(); + + $dc->refresh(); + expect($dc->name)->toBe('Nürnberg')->and($dc->location)->toBe('DE')->and($dc->code)->toBe('nbg'); +}); + +it('deletes an unused datacenter', function () { + $dc = Datacenter::factory()->create(['code' => 'nbg']); + + Livewire::actingAs(admin())->test(ConfirmDeleteDatacenter::class, ['uuid' => $dc->uuid])->call('delete'); + + expect(Datacenter::query()->where('uuid', $dc->uuid)->exists())->toBeFalse(); +}); + +it('refuses to delete a datacenter that still has hosts', function () { + $dc = Datacenter::factory()->create(['code' => 'nbg']); + Host::factory()->create(['datacenter' => 'nbg']); + + Livewire::actingAs(admin())->test(ConfirmDeleteDatacenter::class, ['uuid' => $dc->uuid]) + ->assertSet('hostCount', 1) + ->call('delete'); + + expect(Datacenter::query()->where('uuid', $dc->uuid)->exists())->toBeTrue(); +}); + it('toggles a datacenter active flag', function () { $dc = Datacenter::factory()->create(['active' => true]);