From b50f97568ff0199276495133bc3cb82ad88c4eed Mon Sep 17 00:00:00 2001 From: nexxo Date: Sat, 25 Jul 2026 18:09:17 +0200 Subject: [PATCH] fix(admin): no admin lockout (is_admin fallback); centered modal width; datacenter edit modal + country dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isOperator()/EnsureAdmin/broadcast fall back to is_admin so a legacy admin is never locked out by a stale permission cache - published modal: centered max-w-lg card (dynamic modalWidth class was purged by Tailwind → full-width) - datacenter edit moved to a modal (no row-height jump); location is now a country dropdown (config/countries.php) instead of free text Co-Authored-By: Claude Opus 4.8 --- app/Http/Middleware/EnsureAdmin.php | 3 +- app/Livewire/Admin/Datacenters.php | 49 +----------- app/Livewire/Admin/EditDatacenter.php | 58 ++++++++++++++ app/Models/User.php | 8 +- config/countries.php | 36 +++++++++ lang/de/datacenters.php | 2 + lang/en/datacenters.php | 2 + .../livewire/admin/datacenters.blade.php | 77 ++++++++----------- .../livewire/admin/edit-datacenter.blade.php | 29 +++++++ .../wire-elements-modal/modal.blade.php | 39 +++++----- routes/channels.php | 2 +- tests/Feature/Admin/DatacenterTest.php | 21 +++-- 12 files changed, 206 insertions(+), 120 deletions(-) create mode 100644 app/Livewire/Admin/EditDatacenter.php create mode 100644 config/countries.php create mode 100644 resources/views/livewire/admin/edit-datacenter.blade.php diff --git a/app/Http/Middleware/EnsureAdmin.php b/app/Http/Middleware/EnsureAdmin.php index 110de1c..1b06cb0 100644 --- a/app/Http/Middleware/EnsureAdmin.php +++ b/app/Http/Middleware/EnsureAdmin.php @@ -13,7 +13,8 @@ class EnsureAdmin */ public function handle(Request $request, Closure $next): Response { - abort_unless((bool) $request->user()?->can('console.view'), 403); + // console.view via RBAC, or the legacy is_admin flag as a safety net. + abort_unless((bool) $request->user()?->isOperator(), 403); return $next($request); } diff --git a/app/Livewire/Admin/Datacenters.php b/app/Livewire/Admin/Datacenters.php index 514ace2..dd55569 100644 --- a/app/Livewire/Admin/Datacenters.php +++ b/app/Livewire/Admin/Datacenters.php @@ -16,54 +16,10 @@ class Datacenters extends Component #[Validate('required|string|max:255')] public string $name = ''; - #[Validate('nullable|string|max:255')] + // Country picked from config/countries.php (no manual code typos). + #[Validate('nullable|string|max:2')] 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 - { - $this->authorize('datacenters.manage'); - $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 - { - $this->authorize('datacenters.manage'); - 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 { $this->authorize('datacenters.manage'); @@ -95,6 +51,7 @@ class Datacenters extends Component { return view('livewire.admin.datacenters', [ 'datacenters' => Datacenter::query()->withCount('hosts')->orderBy('name')->get(), + 'countries' => config('countries'), ]); } } diff --git a/app/Livewire/Admin/EditDatacenter.php b/app/Livewire/Admin/EditDatacenter.php new file mode 100644 index 0000000..bc8f7da --- /dev/null +++ b/app/Livewire/Admin/EditDatacenter.php @@ -0,0 +1,58 @@ +where('uuid', $uuid)->firstOrFail(); + $this->uuid = $uuid; + $this->code = $dc->code; + $this->name = $dc->name; + $this->location = (string) $dc->location; + } + + public function save() + { + $this->authorize('datacenters.manage'); + $data = $this->validate(); + + Datacenter::query()->where('uuid', $this->uuid)->update([ + 'name' => $data['name'], + 'location' => $data['location'] ?: null, + ]); + + $this->dispatch('notify', message: __('datacenters.updated')); + + return $this->redirectRoute('admin.datacenters', navigate: true); + } + + public function render() + { + return view('livewire.admin.edit-datacenter', [ + 'countries' => config('countries'), + ]); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 239ab1c..85829fb 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -22,10 +22,14 @@ class User extends Authenticatable /** The five operator roles that grant access to the admin console. */ public const OPERATOR_ROLES = ['Owner', 'Admin', 'Support', 'Billing', 'Read-only']; - /** True when this user is a CluPilot operator (has any admin role). */ + /** + * True when this user is a CluPilot operator. Prefers RBAC roles, but keeps + * the legacy is_admin flag as a fallback so an operator can never be locked + * out of the console by a stale permission cache or a missing role. + */ public function isOperator(): bool { - return $this->hasAnyRole(self::OPERATOR_ROLES); + return $this->is_admin || $this->hasAnyRole(self::OPERATOR_ROLES); } /** diff --git a/config/countries.php b/config/countries.php new file mode 100644 index 0000000..a65211c --- /dev/null +++ b/config/countries.php @@ -0,0 +1,36 @@ + 'Deutschland', + 'AT' => 'Österreich', + 'CH' => 'Schweiz', + 'FI' => 'Finnland', + 'SE' => 'Schweden', + 'NO' => 'Norwegen', + 'DK' => 'Dänemark', + 'NL' => 'Niederlande', + 'BE' => 'Belgien', + 'LU' => 'Luxemburg', + 'FR' => 'Frankreich', + 'GB' => 'Vereinigtes Königreich', + 'IE' => 'Irland', + 'ES' => 'Spanien', + 'PT' => 'Portugal', + 'IT' => 'Italien', + 'PL' => 'Polen', + 'CZ' => 'Tschechien', + 'SK' => 'Slowakei', + 'HU' => 'Ungarn', + 'SI' => 'Slowenien', + 'HR' => 'Kroatien', + 'RO' => 'Rumänien', + 'BG' => 'Bulgarien', + 'GR' => 'Griechenland', + 'EE' => 'Estland', + 'LV' => 'Lettland', + 'LT' => 'Litauen', + 'US' => 'USA', + 'CA' => 'Kanada', +]; diff --git a/lang/de/datacenters.php b/lang/de/datacenters.php index 09ddd2e..0ba0940 100644 --- a/lang/de/datacenters.php +++ b/lang/de/datacenters.php @@ -18,6 +18,8 @@ return [ 'updated' => 'Rechenzentrum aktualisiert.', 'actions' => 'Aktionen', 'edit' => 'Bearbeiten', + 'edit_title' => 'Rechenzentrum bearbeiten', + 'no_country' => 'Kein Land', 'save' => 'Speichern', 'cancel' => 'Abbrechen', 'delete' => 'Löschen', diff --git a/lang/en/datacenters.php b/lang/en/datacenters.php index f290ec9..d7a0530 100644 --- a/lang/en/datacenters.php +++ b/lang/en/datacenters.php @@ -18,6 +18,8 @@ return [ 'updated' => 'Datacenter updated.', 'actions' => 'Actions', 'edit' => 'Edit', + 'edit_title' => 'Edit datacenter', + 'no_country' => 'No country', 'save' => 'Save', 'cancel' => 'Cancel', 'delete' => 'Delete', diff --git a/resources/views/livewire/admin/datacenters.blade.php b/resources/views/livewire/admin/datacenters.blade.php index fb608ec..52064b9 100644 --- a/resources/views/livewire/admin/datacenters.blade.php +++ b/resources/views/livewire/admin/datacenters.blade.php @@ -24,49 +24,32 @@ @foreach ($datacenters as $dc) {{ $dc->code }} - @if ($editingUuid === $dc->uuid) - -
- - -
- @error('editName')

{{ $message }}

@enderror - - -
- - -
- - @else - {{ $dc->name }} - @if ($dc->location)· {{ $dc->location }}@endif - - {{ $dc->hosts_count }} - - + + +
+ - - -
- - -
- - @endif + +
+ @endforeach @@ -79,7 +62,15 @@

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

- +
+ + +
{{ __('datacenters.add') }} diff --git a/resources/views/livewire/admin/edit-datacenter.blade.php b/resources/views/livewire/admin/edit-datacenter.blade.php new file mode 100644 index 0000000..a8763de --- /dev/null +++ b/resources/views/livewire/admin/edit-datacenter.blade.php @@ -0,0 +1,29 @@ +
+
+

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

+ {{ $code }} +
+ +
+
+ + + @error('name')

{{ $message }}

@enderror +
+
+ + + @error('location')

{{ $message }}

@enderror +
+
+ +
+ {{ __('datacenters.cancel') }} + {{ __('datacenters.save') }} +
+
diff --git a/resources/views/vendor/wire-elements-modal/modal.blade.php b/resources/views/vendor/wire-elements-modal/modal.blade.php index cdab72a..d68a94b 100644 --- a/resources/views/vendor/wire-elements-modal/modal.blade.php +++ b/resources/views/vendor/wire-elements-modal/modal.blade.php @@ -14,33 +14,32 @@ class="fixed inset-0 z-[70] overflow-y-auto" style="display: none;" > -
-
-
-
- - + {{-- Backdrop --}} +
+ {{-- Centered panel with a fixed, contextual max width (max-w-lg is + compiled statically; the package's dynamic modalWidth class would be + purged by Tailwind and collapse the modal to full width). --}} +