132 lines
4.9 KiB
PHP
132 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\System;
|
|
|
|
use App\Models\AuditEvent;
|
|
use App\Models\Setting;
|
|
use App\Services\DeploymentService;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\On;
|
|
use Livewire\Attributes\Title;
|
|
use Livewire\Component;
|
|
|
|
/**
|
|
* System settings — dashboard-configurable Domain + TLS and the release channel.
|
|
*
|
|
* Domain & TLS: persists the panel domain + Let's Encrypt admin email (Settings),
|
|
* then renders the matching Caddy site block via DeploymentService for the operator
|
|
* to review. The app NEVER reloads Caddy itself (separate container) — it only
|
|
* stages the config and shows the reload command.
|
|
*
|
|
* Release-Kanal: a stable|beta choice persisted as a Setting (default from
|
|
* config('clusev.channel')), audited on change.
|
|
*/
|
|
#[Layout('layouts.app')]
|
|
#[Title('System — Clusev')]
|
|
class Index extends Component
|
|
{
|
|
/** Valid user-facing release channels with their German descriptions. */
|
|
public const CHANNELS = [
|
|
'stable' => 'Nur getestete, vom Maintainer freigegebene Releases (getaggte Versionen). Empfohlen für den Produktivbetrieb.',
|
|
'beta' => 'Vorabversionen kommender Releases zum Testen neuer Funktionen. Kann instabil sein. Entwicklungs-Builds werden Nutzern nie angeboten.',
|
|
];
|
|
|
|
public string $domain = '';
|
|
|
|
public string $email = '';
|
|
|
|
public string $channel = 'stable';
|
|
|
|
public function mount(DeploymentService $deployment): void
|
|
{
|
|
$this->domain = (string) ($deployment->domain() ?? '');
|
|
$this->email = (string) ($deployment->email() ?? '');
|
|
$channel = Setting::get('release_channel', config('clusev.channel')) ?? 'stable';
|
|
// Clamp to a valid user channel — a stale/legacy value (e.g. 'dev') falls back to stable.
|
|
$this->channel = array_key_exists($channel, self::CHANNELS) ? $channel : 'stable';
|
|
}
|
|
|
|
/** Persist domain + email, then (re)render the Caddy site config for review. */
|
|
public function saveDomain(DeploymentService $deployment): void
|
|
{
|
|
$data = $this->validate([
|
|
'domain' => ['nullable', 'string', 'max:253', 'regex:/^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i'],
|
|
'email' => ['nullable', 'email', 'max:255', 'required_with:domain'],
|
|
], [
|
|
'domain.regex' => 'Bitte eine gültige Domain angeben (z. B. panel.example.com).',
|
|
'email.required_with' => 'Bei gesetzter Domain ist eine Admin-E-Mail für Let\'s Encrypt Pflicht.',
|
|
]);
|
|
|
|
$deployment->setDomain($data['domain'] ?? null);
|
|
$deployment->setEmail($data['email'] ?? null);
|
|
|
|
$this->domain = (string) ($deployment->domain() ?? '');
|
|
$this->email = (string) ($deployment->email() ?? '');
|
|
|
|
$deployment->writeCaddyConfig();
|
|
|
|
$this->audit('system.settings_updated', $this->domain !== '' ? $this->domain : 'bare-ip');
|
|
|
|
$this->dispatch('notify', message: $this->domain !== ''
|
|
? 'Domain gespeichert. Caddy-Konfiguration neu erzeugt.'
|
|
: 'Bare-IP-Modus gespeichert. Caddy-Konfiguration neu erzeugt.');
|
|
}
|
|
|
|
/** Release channel changes are audited (confirmation via the shared modal). */
|
|
public function confirmChannel(string $channel): void
|
|
{
|
|
if (! array_key_exists($channel, self::CHANNELS) || $channel === $this->channel) {
|
|
return;
|
|
}
|
|
|
|
$this->dispatch('openModal',
|
|
component: 'modals.confirm-action',
|
|
arguments: [
|
|
'heading' => 'Release-Kanal wechseln',
|
|
'body' => 'Der Release-Kanal wird auf "'.$channel.'" gesetzt. Updates werden dann aus dieser Quelle bezogen.',
|
|
'confirmLabel' => 'Wechseln',
|
|
'danger' => false,
|
|
'icon' => 'git-branch',
|
|
'auditAction' => 'system.settings_updated',
|
|
'auditTarget' => 'release_channel='.$channel,
|
|
'event' => 'channelChanged',
|
|
'params' => ['channel' => $channel],
|
|
'notify' => 'Release-Kanal auf "'.$channel.'" gesetzt.',
|
|
],
|
|
);
|
|
}
|
|
|
|
#[On('channelChanged')]
|
|
public function applyChannel(string $channel): void
|
|
{
|
|
if (! array_key_exists($channel, self::CHANNELS)) {
|
|
return;
|
|
}
|
|
|
|
Setting::put('release_channel', $channel);
|
|
$this->channel = $channel;
|
|
}
|
|
|
|
private function audit(string $action, string $target): void
|
|
{
|
|
AuditEvent::create([
|
|
'user_id' => Auth::id(),
|
|
'actor' => Auth::user()?->name ?? 'system',
|
|
'action' => $action,
|
|
'target' => $target,
|
|
'ip' => request()->ip(),
|
|
]);
|
|
}
|
|
|
|
public function render(DeploymentService $deployment)
|
|
{
|
|
return view('livewire.system.index', [
|
|
'hasTls' => $this->domain !== '',
|
|
'caddyConfig' => $deployment->renderCaddyConfig(),
|
|
'reloadHint' => $deployment->reloadHint(),
|
|
'channels' => self::CHANNELS,
|
|
]);
|
|
}
|
|
}
|