diff --git a/app/Livewire/Admin/EditDatacenter.php b/app/Livewire/Admin/EditDatacenter.php index 72c5241..f923360 100644 --- a/app/Livewire/Admin/EditDatacenter.php +++ b/app/Livewire/Admin/EditDatacenter.php @@ -26,6 +26,15 @@ class EditDatacenter extends ModalComponent /** The location present when the modal opened — a pre-curation free-form value. */ public string $originalLocation = ''; + /** + * Whether new hosts and orders may still be placed here. + * + * The column and the scope existed; the form did not offer it, so a + * datacenter could be created and never switched off again — the one + * lifecycle action a location actually needs. + */ + public bool $active = true; + public function mount(string $uuid): void { $this->authorize('datacenters.manage'); // modals are reachable without the route middleware @@ -35,6 +44,7 @@ class EditDatacenter extends ModalComponent $this->name = $dc->name; $this->location = (string) $dc->location; $this->originalLocation = (string) $dc->location; + $this->active = (bool) $dc->active; } public function save() @@ -53,14 +63,27 @@ class EditDatacenter extends ModalComponent $data = $this->validate([ 'name' => 'required|string|max:255', 'location' => ['nullable', Rule::in($allowed)], // array form — a legacy value may contain commas + 'active' => 'boolean', ]); + // Only on the transition, and compared against what is STORED: an + // already-inactive location would otherwise announce that it had just + // been switched off every time its name was edited. + $justDeactivated = $dc->active && ! $data['active']; + $hostsLeftRunning = $justDeactivated ? $dc->hosts()->count() : 0; + Datacenter::query()->where('uuid', $this->uuid)->update([ 'name' => $data['name'], 'location' => $data['location'] ?: null, + 'active' => $data['active'], ]); - $this->dispatch('notify', message: __('datacenters.updated')); + // One message, not two. Both go through the same toast, and the second + // replaces the first — so the generic "saved" would swallow the only + // sentence that says the hosts there are still running. + $this->dispatch('notify', message: $hostsLeftRunning > 0 + ? __('datacenters.deactivated_with_hosts', ['n' => $hostsLeftRunning]) + : __('datacenters.updated')); return $this->redirectRoute('admin.datacenters', navigate: true); } diff --git a/app/Livewire/Admin/Settings.php b/app/Livewire/Admin/Settings.php index 9aa7bd7..f435fc4 100644 --- a/app/Livewire/Admin/Settings.php +++ b/app/Livewire/Admin/Settings.php @@ -24,6 +24,8 @@ use Livewire\Component; #[Layout('layouts.admin')] class Settings extends Component { + use \App\Livewire\Concerns\ChangesOwnPassword; + // My account #[Validate('required|string|max:255')] public string $name = ''; diff --git a/app/Livewire/Concerns/ChangesOwnPassword.php b/app/Livewire/Concerns/ChangesOwnPassword.php new file mode 100644 index 0000000..aa18ccd --- /dev/null +++ b/app/Livewire/Concerns/ChangesOwnPassword.php @@ -0,0 +1,67 @@ +validate([ + 'currentPassword' => 'required|string', + 'newPassword' => 'required|string', + 'newPasswordConfirmation' => 'required|string', + ]); + + if (! Hash::check($this->currentPassword, auth()->user()->password)) { + $this->addError('currentPassword', __('admin_settings.password_wrong')); + + return; + } + + try { + app(UpdateUserPassword::class)->update(auth()->user(), [ + 'current_password' => $this->currentPassword, + 'password' => $this->newPassword, + 'password_confirmation' => $this->newPasswordConfirmation, + ]); + } catch (ValidationException $e) { + foreach ($e->errors() as $field => $messages) { + $this->addError(match ($field) { + 'current_password' => 'currentPassword', + 'password' => 'newPassword', + default => $field, + }, $messages[0]); + } + + return; + } + + $this->reset('currentPassword', 'newPassword', 'newPasswordConfirmation'); + $this->dispatch('notify', message: __('admin_settings.password_changed')); + } +} diff --git a/app/Livewire/Settings.php b/app/Livewire/Settings.php index 059cad8..e131dcc 100644 --- a/app/Livewire/Settings.php +++ b/app/Livewire/Settings.php @@ -11,6 +11,8 @@ use Livewire\WithFileUploads; class Settings extends Component { + use \App\Livewire\Concerns\ChangesOwnPassword; + use ResolvesCustomer, WithFileUploads; // Company / billing profile diff --git a/config/fortify.php b/config/fortify.php index b88737f..b13ee9c 100644 --- a/config/fortify.php +++ b/config/fortify.php @@ -173,7 +173,10 @@ return [ // Features::resetPasswords(), // Features::emailVerification(), // Features::updateProfileInformation(), - // Features::updatePasswords(), + // Everyone must be able to change their own password. It was off, so + // an account created with a generated password was stuck with it — and + // the only way to change one was a shell on the server. + Features::updatePasswords(), Features::twoFactorAuthentication([ 'confirm' => true, 'confirmPassword' => true, diff --git a/lang/de/admin_settings.php b/lang/de/admin_settings.php index 3fe24c4..c82d861 100644 --- a/lang/de/admin_settings.php +++ b/lang/de/admin_settings.php @@ -103,4 +103,13 @@ return [ 'failed' => 'fehlgeschlagen', 'running' => 'läuft', ], + + 'password_title' => 'Passwort ändern', + 'password_sub' => 'Gilt sofort. Andere Sitzungen bleiben angemeldet.', + 'password_current' => 'Aktuelles Passwort', + 'password_new' => 'Neues Passwort', + 'password_repeat' => 'Wiederholen', + 'password_save' => 'Passwort ändern', + 'password_wrong' => 'Das aktuelle Passwort stimmt nicht.', + 'password_changed' => 'Passwort geändert.', ]; diff --git a/lang/de/datacenters.php b/lang/de/datacenters.php index 0ba0940..8ae58f6 100644 --- a/lang/de/datacenters.php +++ b/lang/de/datacenters.php @@ -28,4 +28,7 @@ return [ '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.', + 'active_label' => 'Für neue Platzierungen verfügbar', + 'active_hint' => 'Abgeschaltet werden hier keine neuen Hosts oder Bestellungen mehr platziert. Was bereits läuft, läuft weiter.', + 'deactivated_with_hosts' => 'Standort abgeschaltet — :n Host(s) laufen dort weiter.', ]; diff --git a/lang/en/admin_settings.php b/lang/en/admin_settings.php index 21d6a59..f3acd9f 100644 --- a/lang/en/admin_settings.php +++ b/lang/en/admin_settings.php @@ -103,4 +103,13 @@ return [ 'failed' => 'failed', 'running' => 'running', ], + + 'password_title' => 'Change password', + 'password_sub' => 'Takes effect immediately. Other sessions stay signed in.', + 'password_current' => 'Current password', + 'password_new' => 'New password', + 'password_repeat' => 'Repeat', + 'password_save' => 'Change password', + 'password_wrong' => 'The current password is not correct.', + 'password_changed' => 'Password changed.', ]; diff --git a/lang/en/datacenters.php b/lang/en/datacenters.php index d7a0530..232c165 100644 --- a/lang/en/datacenters.php +++ b/lang/en/datacenters.php @@ -28,4 +28,7 @@ return [ 'delete_confirm' => 'Delete permanently', 'delete_blocked_title' => 'Cannot delete', 'delete_blocked_body' => '“:name” still has :count host(s). Remove or move the hosts first.', + 'active_label' => 'Available for new placements', + 'active_hint' => 'Switched off, no new hosts or orders are placed here. Anything already running keeps running.', + 'deactivated_with_hosts' => 'Location switched off — :n host(s) keep running there.', ]; diff --git a/resources/views/livewire/admin/edit-datacenter.blade.php b/resources/views/livewire/admin/edit-datacenter.blade.php index 5b4aee0..5165335 100644 --- a/resources/views/livewire/admin/edit-datacenter.blade.php +++ b/resources/views/livewire/admin/edit-datacenter.blade.php @@ -25,6 +25,13 @@ + {{-- The one lifecycle action a location needs, and the form did not offer + it: a datacenter could be created and never switched off again. --}} +
{{ __('datacenters.active_hint') }}
+