diff --git a/app/Livewire/Ui/Security/SslCertificatesTable.php b/app/Livewire/Ui/Security/SslCertificatesTable.php
index d0a1972..6a0f3be 100644
--- a/app/Livewire/Ui/Security/SslCertificatesTable.php
+++ b/app/Livewire/Ui/Security/SslCertificatesTable.php
@@ -2,6 +2,7 @@
namespace App\Livewire\Ui\Security;
+use App\Models\Setting;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
@@ -12,9 +13,26 @@ class SslCertificatesTable extends Component
{
public array $certs = [];
+ // Domains (aus Einstellungen)
+ public string $uiDomain = '';
+ public string $webmailDomain = '';
+ public string $mailDomain = '';
+
+ // Provisioning
+ public bool $sslProvisioning = false;
+ public bool $sslDone = false;
+ public array $sslProgress = ['ui' => 'pending', 'webmail' => 'pending', 'mail' => 'pending'];
+
+ private const SSL_STATE_DIR = '/var/lib/mailwolt/wizard';
+
public function mount(): void
{
+ $this->uiDomain = (string) Setting::get('ui_domain', '');
+ $this->webmailDomain = (string) Setting::get('webmail_domain', '');
+ $this->mailDomain = (string) Setting::get('mail_domain', '');
+
$this->certs = $this->loadCertificates();
+ $this->restoreSslProvisioningState();
}
public function refresh(): void
@@ -24,6 +42,55 @@ class SslCertificatesTable extends Component
text: 'Zertifikatsliste wurde neu geladen.', duration: 3000);
}
+ public function startSslProvisioning(): void
+ {
+ if (! ($this->uiDomain && $this->webmailDomain && $this->mailDomain)) {
+ $this->dispatch('toast', type: 'warn', badge: 'SSL',
+ title: 'Domains fehlen',
+ text: 'Bitte erst alle Domains unter Einstellungen speichern.', duration: 6000);
+ return;
+ }
+
+ @mkdir(self::SSL_STATE_DIR, 0755, true);
+ @unlink(self::SSL_STATE_DIR . '/done');
+ foreach (['ui', 'mail', 'webmail'] as $k) {
+ file_put_contents(self::SSL_STATE_DIR . "/{$k}", 'pending');
+ }
+
+ $this->sslProgress = ['ui' => 'pending', 'webmail' => 'pending', 'mail' => 'pending'];
+ $this->sslDone = false;
+ $this->sslProvisioning = true;
+
+ $artisan = base_path('artisan');
+ $cmd = sprintf(
+ 'nohup php %s mailwolt:wizard-domains --ui=%s --mail=%s --webmail=%s --ssl=1 > /dev/null 2>&1 &',
+ escapeshellarg($artisan),
+ escapeshellarg($this->uiDomain),
+ escapeshellarg($this->mailDomain),
+ escapeshellarg($this->webmailDomain),
+ );
+ @shell_exec($cmd);
+ }
+
+ public function pollSsl(): void
+ {
+ if (! $this->sslProvisioning || $this->sslDone) return;
+
+ foreach (['ui', 'mail', 'webmail'] as $key) {
+ $file = self::SSL_STATE_DIR . "/{$key}";
+ $this->sslProgress[$key] = is_readable($file)
+ ? trim((string) @file_get_contents($file))
+ : 'pending';
+ }
+
+ $done = @file_get_contents(self::SSL_STATE_DIR . '/done');
+ if ($done !== false) {
+ $this->sslDone = true;
+ $this->sslProvisioning = false;
+ $this->certs = $this->loadCertificates();
+ }
+ }
+
public function renew(string $name): void
{
$safe = preg_replace('/[^a-z0-9._-]/i', '', $name);
@@ -44,6 +111,21 @@ class SslCertificatesTable extends Component
}
}
+ private function restoreSslProvisioningState(): void
+ {
+ $doneFile = self::SSL_STATE_DIR . '/done';
+ if (! file_exists($doneFile)) return;
+
+ $this->sslDone = true;
+ $this->sslProvisioning = false;
+ foreach (['ui', 'mail', 'webmail'] as $key) {
+ $file = self::SSL_STATE_DIR . "/{$key}";
+ if (is_readable($file)) {
+ $this->sslProgress[$key] = trim((string) @file_get_contents($file));
+ }
+ }
+ }
+
private function loadCertificates(): array
{
$out = (string) @shell_exec('sudo -n /usr/bin/certbot certificates 2>&1');
@@ -63,6 +145,13 @@ class SslCertificatesTable extends Component
$expiryRaw = trim($expiryM[1] ?? '');
$daysLeft = null;
$expired = false;
+ $expiryDate = null;
+
+ // Datum extrahieren (Format: "2026-07-24 10:30:00+00:00 (VALID: 88 days)")
+ if (preg_match('/(\d{4}-\d{2}-\d{2})/', $expiryRaw, $dateM)) {
+ $ts = strtotime($dateM[1]);
+ $expiryDate = $ts ? date('d.m.Y', $ts) : null;
+ }
if (preg_match('/VALID: (\d+) days/i', $expiryRaw, $dM)) {
$daysLeft = (int) $dM[1];
@@ -75,12 +164,12 @@ class SslCertificatesTable extends Component
$domains = $domainsRaw !== '' ? array_values(array_filter(explode(' ', $domainsRaw))) : [];
$certs[] = [
- 'name' => trim($nameM[1]),
- 'domains' => $domains,
- 'expiry' => $expiryRaw,
- 'days_left' => $daysLeft,
- 'expired' => $expired,
- 'cert_path' => trim($certM[1] ?? ''),
+ 'name' => trim($nameM[1]),
+ 'domains' => $domains,
+ 'expiry_date' => $expiryDate,
+ 'days_left' => $daysLeft,
+ 'expired' => $expired,
+ 'cert_path' => trim($certM[1] ?? ''),
];
}
diff --git a/app/Livewire/Ui/System/SettingsForm.php b/app/Livewire/Ui/System/SettingsForm.php
index 8977311..24ea737 100644
--- a/app/Livewire/Ui/System/SettingsForm.php
+++ b/app/Livewire/Ui/System/SettingsForm.php
@@ -24,11 +24,8 @@ class SettingsForm extends Component
public string $mail_domain = '';
public string $webmail_domain = '';
- // SSL provisioning
- public bool $sslProvisioning = false;
- public bool $sslDone = false;
- public array $sslProgress = ['ui' => 'pending', 'webmail' => 'pending', 'mail' => 'pending'];
- public array $sslCerts = [];
+ // SSL-Status (read-only, für Anzeige in Einstellungen)
+ public array $sslCerts = [];
private const SSL_STATE_DIR = '/var/lib/mailwolt/wizard';
@@ -132,7 +129,6 @@ class SettingsForm extends Component
}
$this->loadSslStatus();
- $this->restoreSslProvisioningState();
$this->rate_limit = (int) Setting::get('rate_limit', $this->rate_limit);
$this->password_min = (int) Setting::get('password_min', $this->password_min);
@@ -217,72 +213,6 @@ class SettingsForm extends Component
return rtrim($value, '/');
}
- public function openSslModal(): void
- {
- if (! ($this->ui_domain && $this->mail_domain && $this->webmail_domain)) {
- $this->dispatch('toast', type: 'warn', badge: 'SSL',
- title: 'Domains fehlen',
- text: 'Bitte erst alle drei Domains speichern.', duration: 5000);
- return;
- }
- $this->dispatch('openModal', component: 'ui.system.modal.ssl-provision-modal');
- }
-
- #[\Livewire\Attributes\On('ssl:provision')]
- public function provisionSsl(): void
- {
- $this->applyDomains(true);
- }
-
- public function startSslProvisioning(): void
- {
- if (! ($this->ui_domain && $this->webmail_domain)) {
- $this->dispatch('toast', type: 'warn', badge: 'SSL',
- title: 'Domains fehlen',
- text: 'Bitte erst UI-Domain und Webmail-Domain speichern.', duration: 5000);
- return;
- }
-
- @mkdir(self::SSL_STATE_DIR, 0755, true);
- @unlink(self::SSL_STATE_DIR . '/done');
- foreach (['ui', 'mail', 'webmail'] as $k) {
- file_put_contents(self::SSL_STATE_DIR . "/{$k}", 'pending');
- }
-
- $this->sslProgress = ['ui' => 'pending', 'webmail' => 'pending', 'mail' => 'pending'];
- $this->sslDone = false;
- $this->sslProvisioning = true;
-
- $artisan = base_path('artisan');
- $cmd = sprintf(
- 'nohup php %s mailwolt:wizard-domains --ui=%s --mail=%s --webmail=%s --ssl=1 > /dev/null 2>&1 &',
- escapeshellarg($artisan),
- escapeshellarg($this->ui_domain),
- escapeshellarg($this->mail_domain),
- escapeshellarg($this->webmail_domain),
- );
- @shell_exec($cmd);
- }
-
- public function pollSsl(): void
- {
- if (! $this->sslProvisioning || $this->sslDone) return;
-
- foreach (['ui', 'mail', 'webmail'] as $key) {
- $file = self::SSL_STATE_DIR . "/{$key}";
- $this->sslProgress[$key] = is_readable($file)
- ? trim((string) @file_get_contents($file))
- : 'pending';
- }
-
- $done = @file_get_contents(self::SSL_STATE_DIR . '/done');
- if ($done !== false) {
- $this->sslDone = true;
- $this->sslProvisioning = false;
- $this->loadSslStatus();
- }
- }
-
public function loadSslStatus(): void
{
clearstatcache(true);
@@ -335,21 +265,6 @@ class SettingsForm extends Component
$this->sslCerts = $certs;
}
- private function restoreSslProvisioningState(): void
- {
- $doneFile = self::SSL_STATE_DIR . '/done';
- if (! file_exists($doneFile)) return;
-
- $this->sslDone = true;
- $this->sslProvisioning = false;
- foreach (['ui', 'mail', 'webmail'] as $key) {
- $file = self::SSL_STATE_DIR . "/{$key}";
- if (is_readable($file)) {
- $this->sslProgress[$key] = trim((string) @file_get_contents($file));
- }
- }
- }
-
public function updatedUiDomain(): void { $this->validateOnly('ui_domain', $this->domainRules(), $this->domainMessages()); }
public function updatedMailDomain(): void { $this->validateOnly('mail_domain', $this->domainRules(), $this->domainMessages()); }
public function updatedWebmailDomain(): void { $this->validateOnly('webmail_domain', $this->domainRules(), $this->domainMessages()); }
diff --git a/resources/views/livewire/ui/security/ssl-certificates-table.blade.php b/resources/views/livewire/ui/security/ssl-certificates-table.blade.php
index 74961e7..12ec0f9 100644
--- a/resources/views/livewire/ui/security/ssl-certificates-table.blade.php
+++ b/resources/views/livewire/ui/security/ssl-certificates-table.blade.php
@@ -2,6 +2,9 @@
certbot certonly aus um ein Zertifikat zu erstellen