diff --git a/app/Livewire/Admin/ConfirmSwitchMode.php b/app/Livewire/Admin/ConfirmSwitchMode.php new file mode 100644 index 0000000..6f01e6a --- /dev/null +++ b/app/Livewire/Admin/ConfirmSwitchMode.php @@ -0,0 +1,46 @@ +authorize('secrets.manage'); + + // Ein Wert aus dem Aufrufort dieses Modals — dieselbe Prüfung, die + // switchMode() gleich noch einmal macht: ein unbekannter Modus 404t + // hier, statt eine Bestätigung für etwas zu zeigen, das nachher + // ohnehin still nichts tut. + abort_if(OperatingMode::tryFrom($mode) === null, 404); + + $this->mode = $mode; + } + + public function confirm(): void + { + $this->authorize('secrets.manage'); + $this->dispatch('mode-switch-confirmed', mode: $this->mode); + $this->closeModal(); + } + + public function render() + { + return view('livewire.admin.confirm-switch-mode'); + } +} diff --git a/app/Livewire/Admin/Integrations.php b/app/Livewire/Admin/Integrations.php index bd0f8e7..a5ab5b3 100644 --- a/app/Livewire/Admin/Integrations.php +++ b/app/Livewire/Admin/Integrations.php @@ -7,6 +7,7 @@ use App\Services\Deployment\UpdateChannel; use App\Services\Env\EnvFileEditor; use App\Services\Env\InvalidEnvContentException; use App\Services\Secrets\SecretVault; +use App\Support\OperatingMode; use App\Support\ProvisioningSettings; use App\Support\Settings; use Illuminate\Support\Facades\Artisan; @@ -188,6 +189,34 @@ class Integrations extends Component } } + // ---- The operating mode — governs every vault entry below it. ---- + + /** + * Den Betriebsmodus umlegen. + * + * Hinter derselben Sperre wie der Tresor — `secrets.manage` plus bestätigtes + * Passwort — weil der Wechsel auf Live der Moment ist, ab dem echtes Geld + * fließt. Das ist keine Umschaltfläche zum Danebenklicken. + */ + #[On('mode-switch-confirmed')] + public function switchMode(string $mode): void + { + $this->guardSecrets(); + + // Ein String aus dem Browser. tryFrom, nicht from. + $target = OperatingMode::tryFrom($mode); + + if ($target === null) { + return; + } + + OperatingMode::set($target); + + $this->dispatch('notify', message: __('integrations.mode_switched', [ + 'mode' => __('readiness.mode.'.$target->value), + ])); + } + // ---- Plain settings — App\Support\Settings, hosts.manage, no password. ---- public function saveInfra(): void @@ -462,6 +491,7 @@ class Integrations extends Component 'canInfra' => $canInfra, 'unlocked' => $unlocked, 'usable' => $vault->isUsable(), + 'mode' => OperatingMode::current(), 'restart' => $restart, 'entries' => collect(SecretVault::REGISTRY) ->map(fn (array $meta, string $key) => [ diff --git a/lang/de/integrations.php b/lang/de/integrations.php index 4551151..b10ccb7 100644 --- a/lang/de/integrations.php +++ b/lang/de/integrations.php @@ -4,6 +4,20 @@ return [ 'title' => 'Integrationen', 'subtitle' => 'Angebundene Dienste, gruppiert nach Zweck — Zugangsdaten und Einstellungen nebeneinander, nicht auf getrennten Seiten nach Speicherort.', + // Der Umschalter — über allen Zugangsdaten, weil er regiert, welcher + // Platz je Eintrag gilt. + 'mode_title' => 'Betriebsmodus', + 'mode_body' => 'Entscheidet, welcher Platz je Zugangsdatum unten gilt. Ist der Testplatz leer, gilt im Testbetrieb der Live-Platz — außer beim Stripe-Schlüssel, der in keine Richtung zurückfällt.', + 'mode_switch_to_test' => 'Zu Testbetrieb wechseln', + 'mode_switch_to_live' => 'Zu Livebetrieb wechseln', + 'mode_switched' => 'Betriebsmodus umgelegt: :mode.', + 'mode_switch_title_test' => 'Wirklich zu Testbetrieb wechseln?', + 'mode_switch_body_test' => 'Ab sofort gilt für jedes Zugangsdatum der Testplatz — oder, wo dieser leer ist, der Live-Platz. Zahlungen laufen weiter über den aktiven Stripe-Schlüssel des jeweiligen Modus.', + 'mode_switch_confirm_test' => 'Umlegen', + 'mode_switch_title_live' => 'Wirklich zu Livebetrieb wechseln?', + 'mode_switch_body_live' => 'Ab sofort fließt echtes Geld: jede Bestellung wird über den Live-Stripe-Schlüssel abgerechnet, nicht mehr über den Testschlüssel. Das ist keine Umschaltfläche zum Danebenklicken.', + 'mode_switch_confirm_live' => 'Umlegen', + 'payments_title' => 'Zahlungen (Stripe)', 'payments_body' => 'Der Secret Key, mit dem Zahlungen entgegengenommen und Webhooks geprüft werden.', @@ -96,4 +110,4 @@ return [ 'platform' => 'Plattform', 'env' => 'Umgebung (.env)', ], -]; \ No newline at end of file +]; diff --git a/lang/en/integrations.php b/lang/en/integrations.php index 24ea100..0c13a19 100644 --- a/lang/en/integrations.php +++ b/lang/en/integrations.php @@ -4,6 +4,20 @@ return [ 'title' => 'Integrations', 'subtitle' => 'Connected services, grouped by purpose — credentials and settings side by side, not on separate pages by storage mechanism.', + // The switch — above every credential, because it governs which slot of + // each entry below applies. + 'mode_title' => 'Operating mode', + 'mode_body' => 'Decides which slot of each credential below applies. In test mode, an empty test slot falls back to the live slot — except the Stripe key, which does not fall back either way.', + 'mode_switch_to_test' => 'Switch to test mode', + 'mode_switch_to_live' => 'Switch to live mode', + 'mode_switched' => 'Operating mode switched: :mode.', + 'mode_switch_title_test' => 'Really switch to test mode?', + 'mode_switch_body_test' => 'From now on, every credential uses its test slot — or, where that one is empty, the live slot. Payments continue to go through whichever Stripe key belongs to the active mode.', + 'mode_switch_confirm_test' => 'Switch', + 'mode_switch_title_live' => 'Really switch to live mode?', + 'mode_switch_body_live' => 'From now on, real money moves: every order is charged through the live Stripe key, not the test one. This is not a switch to click by accident.', + 'mode_switch_confirm_live' => 'Switch', + 'payments_title' => 'Payments (Stripe)', 'payments_body' => 'The secret key used to accept payments and verify webhooks.', @@ -96,4 +110,4 @@ return [ 'platform' => 'Platform', 'env' => 'Environment (.env)', ], -]; \ No newline at end of file +]; diff --git a/resources/views/components/ui/icon.blade.php b/resources/views/components/ui/icon.blade.php index e69ec28..24ea9cc 100644 --- a/resources/views/components/ui/icon.blade.php +++ b/resources/views/components/ui/icon.blade.php @@ -43,6 +43,7 @@ 'globe' => '', 'send' => '', 'plug' => '', + 'beaker' => '', ]; $body = $icons[$name] ?? ''; diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index 36b12c1..9e1fd8e 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -42,6 +42,16 @@
+ {{-- "Bin ich im Test?" darf keine Frage sein, die man erst + durch Navigieren zur Integrationsseite beantwortet — die + Plakette steht im Kopf jeder Konsolenseite, solange der + Modus test ist, und verschwindet von selbst im Livebetrieb. --}} + @if (\App\Support\OperatingMode::current()->isTest()) + + + {{ __('readiness.mode.test') }} + + @endif @auth