Rechtebechebung für User mit Sudorechte
parent
28129cb989
commit
f8934b7a9a
|
|
@ -3,16 +3,33 @@
|
||||||
namespace App\Livewire\Auth;
|
namespace App\Livewire\Auth;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class LoginForm extends Component
|
class LoginForm extends Component
|
||||||
{
|
{
|
||||||
|
public ?array $banner = [];
|
||||||
|
public bool $showBanner = false;
|
||||||
|
|
||||||
public string $name = '';
|
public string $name = '';
|
||||||
public string $password = '';
|
public string $password = '';
|
||||||
public ?string $error = null;
|
public ?string $error = null;
|
||||||
public bool $show = false;
|
public bool $show = false;
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
// Flash nur EINMAL ziehen
|
||||||
|
$flash = session()->pull('login_banner');
|
||||||
|
if ($flash) {
|
||||||
|
$this->banner = $flash;
|
||||||
|
$this->showBanner = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dismissBanner(): void
|
||||||
|
{
|
||||||
|
$this->showBanner = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function login()
|
public function login()
|
||||||
{
|
{
|
||||||
|
|
@ -28,7 +45,7 @@ class LoginForm extends Component
|
||||||
|
|
||||||
if (Auth::attempt([$field => $this->name, 'password' => $this->password], true)) {
|
if (Auth::attempt([$field => $this->name, 'password' => $this->password], true)) {
|
||||||
request()->session()->regenerate();
|
request()->session()->regenerate();
|
||||||
return redirect()->intended(route('setup.wizard')) ;
|
return redirect()->intended(route('ui.dashboard'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->error = 'Ungültige Zugangsdaten.';
|
$this->error = 'Ungültige Zugangsdaten.';
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,13 @@ class SignupForm extends Component
|
||||||
// nach erstem User: Signup deaktivieren
|
// nach erstem User: Signup deaktivieren
|
||||||
if ($isFirstUser) {
|
if ($isFirstUser) {
|
||||||
Setting::set('system.signup_enabled', 0); // Redis + DB
|
Setting::set('system.signup_enabled', 0); // Redis + DB
|
||||||
|
return redirect()
|
||||||
|
->route('login')
|
||||||
|
->with('login_banner', [
|
||||||
|
'type' => 'success',
|
||||||
|
'title' => 'Registrierung abgeschlossen',
|
||||||
|
'message' => 'Dein Konto wurde erfolgreich erstellt. Du kannst dich jetzt anmelden.',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ class DomainDnsModal extends ModalComponent
|
||||||
public array $static = [];
|
public array $static = [];
|
||||||
/** @var array<int,array<string,string|int|null>> */
|
/** @var array<int,array<string,string|int|null>> */
|
||||||
public array $dynamic = [];
|
public array $dynamic = [];
|
||||||
|
/** @var array<int,array<string,string|int|null>> */
|
||||||
|
public array $optional = [];
|
||||||
|
|
||||||
public static function modalMaxWidth(): string
|
public static function modalMaxWidth(): string
|
||||||
{
|
{
|
||||||
|
|
@ -37,6 +39,7 @@ class DomainDnsModal extends ModalComponent
|
||||||
'TXT' => 'bg-violet-500/20 text-violet-300',
|
'TXT' => 'bg-violet-500/20 text-violet-300',
|
||||||
'SRV' => 'bg-rose-500/20 text-rose-300',
|
'SRV' => 'bg-rose-500/20 text-rose-300',
|
||||||
'TLSA' => 'bg-red-500/20 text-red-300',
|
'TLSA' => 'bg-red-500/20 text-red-300',
|
||||||
|
'OPTIONAL' => 'bg-gray-500/20 text-gray-300',
|
||||||
];
|
];
|
||||||
|
|
||||||
$d = Domain::findOrFail($domainId);
|
$d = Domain::findOrFail($domainId);
|
||||||
|
|
@ -59,7 +62,6 @@ class DomainDnsModal extends ModalComponent
|
||||||
|
|
||||||
// --- Statische Infrastruktur (für alle Domains gleich) ---
|
// --- Statische Infrastruktur (für alle Domains gleich) ---
|
||||||
$this->static = [
|
$this->static = [
|
||||||
['type' => 'MX', 'name' => $base, 'value' => "10 {$mailServerFqdn}."],
|
|
||||||
['type' => 'A', 'name' => $mailServerFqdn, 'value' => $ipv4],
|
['type' => 'A', 'name' => $mailServerFqdn, 'value' => $ipv4],
|
||||||
['type' => 'PTR', 'name' => $this->ptrFromIPv4($ipv4), 'value' => $tlsa->host],
|
['type' => 'PTR', 'name' => $this->ptrFromIPv4($ipv4), 'value' => $tlsa->host],
|
||||||
];
|
];
|
||||||
|
|
@ -78,7 +80,7 @@ class DomainDnsModal extends ModalComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Domain-spezifisch ---
|
// --- Domain-spezifisch ---
|
||||||
$spf = "v=spf1 a mx ip4:{$ipv4} ip6:{$ipv6} ~all";
|
$spf = "v=spf1 ip4:{$ipv4} ip6:{$ipv6} mx -all";
|
||||||
$dmarc = "v=DMARC1; p=none; rua=mailto:dmarc@{$this->domainName}; pct=100";
|
$dmarc = "v=DMARC1; p=none; rua=mailto:dmarc@{$this->domainName}; pct=100";
|
||||||
|
|
||||||
$dkim = DB::table('dkim_keys')
|
$dkim = DB::table('dkim_keys')
|
||||||
|
|
@ -90,19 +92,24 @@ class DomainDnsModal extends ModalComponent
|
||||||
: ($dkim->public_key_txt ?? 'v=DKIM1; k=rsa; p=');
|
: ($dkim->public_key_txt ?? 'v=DKIM1; k=rsa; p=');
|
||||||
|
|
||||||
$this->dynamic = [
|
$this->dynamic = [
|
||||||
|
['type' => 'MX', 'name' => $this->domainName, 'value' => "10 {$mailServerFqdn}."],
|
||||||
|
|
||||||
['type' => 'CNAME', 'name' => "autoconfig.$this->domainName", 'value' => "$mailServerFqdn."],
|
['type' => 'CNAME', 'name' => "autoconfig.$this->domainName", 'value' => "$mailServerFqdn."],
|
||||||
['type' => 'CNAME', 'name' => "autodiscover.$this->domainName", 'value' => "$mailServerFqdn."],
|
['type' => 'CNAME', 'name' => "autodiscover.$this->domainName", 'value' => "$mailServerFqdn."],
|
||||||
|
|
||||||
// SRV Records für Autodiscover und Maildienste
|
|
||||||
['type' => 'SRV', 'name' => "_autodiscover._tcp.$this->domainName", 'value' => "0 0 443 {$mailServerFqdn}."],
|
|
||||||
['type' => 'SRV', 'name' => "_imaps._tcp.$this->domainName", 'value' => "0 0 993 {$mailServerFqdn}."],
|
|
||||||
['type' => 'SRV', 'name' => "_pop3s._tcp.$this->domainName", 'value' => "0 0 995 {$mailServerFqdn}."],
|
|
||||||
['type' => 'SRV', 'name' => "_submission._tcp.$this->domainName", 'value' => "0 0 587 {$mailServerFqdn}."],
|
|
||||||
|
|
||||||
// TXT Records
|
// TXT Records
|
||||||
['type' => 'TXT', 'name' => $this->domainName, 'value' => $spf, 'helpLabel' => 'SPF Record Syntax', 'helpUrl' => 'http://www.open-spf.org/SPF_Record_Syntax/'],
|
['type' => 'TXT', 'name' => $this->domainName, 'value' => $spf, 'helpLabel' => 'SPF Record Syntax', 'helpUrl' => 'http://www.open-spf.org/SPF_Record_Syntax/'],
|
||||||
['type' => 'TXT', 'name' => "_dmarc.{$this->domainName}", 'value' => $dmarc, 'helpLabel' => 'DMARC Assistant', 'helpUrl' => 'https://www.kitterman.com/dmarc/assistant.html'],
|
['type' => 'TXT', 'name' => "_dmarc.{$this->domainName}", 'value' => $dmarc, 'helpLabel' => 'DMARC Assistant', 'helpUrl' => 'https://www.kitterman.com/dmarc/assistant.html'],
|
||||||
['type' => 'TXT', 'name' => $dkimHost, 'value' => $dkimTxt, 'helpLabel' => 'DKIM Inspector', 'helpUrl' => 'https://dkimvalidator.com/'],
|
['type' => 'TXT', 'name' => $dkimHost, 'value' => $dkimTxt, 'helpLabel' => 'DKIM Inspector', 'helpUrl' => 'https://dkimvalidator.com/'],
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->optional = [
|
||||||
|
// SRV Records für Autodiscover und Maildienste
|
||||||
|
['type' => 'SRV', 'name' => "_autodiscover._tcp.$this->domainName", 'value' => "0 0 443 {$mailServerFqdn}."],
|
||||||
|
['type' => 'SRV', 'name' => "_imaps._tcp.$this->domainName", 'value' => "0 0 993 {$mailServerFqdn}."],
|
||||||
|
['type' => 'SRV', 'name' => "_pop3s._tcp.$this->domainName", 'value' => "0 0 995 {$mailServerFqdn}."],
|
||||||
|
['type' => 'SRV', 'name' => "_submission._tcp.$this->domainName", 'value' => "0 0 587 {$mailServerFqdn}."],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,8 @@
|
||||||
|
|
||||||
namespace App\Observers;
|
namespace App\Observers;
|
||||||
|
|
||||||
use App\Jobs\InstallDkimKey;
|
|
||||||
use App\Jobs\RemoveDkimKey;
|
|
||||||
use App\Models\DkimKey;
|
|
||||||
use App\Models\Domain;
|
use App\Models\Domain;
|
||||||
use App\Services\DkimService;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class DomainObserver
|
class DomainObserver
|
||||||
{
|
{
|
||||||
|
|
@ -16,76 +13,54 @@ class DomainObserver
|
||||||
*/
|
*/
|
||||||
public function created(Domain $domain): void
|
public function created(Domain $domain): void
|
||||||
{
|
{
|
||||||
if ($domain->is_server) {
|
if ($domain->is_server) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$selector = (string) config('mailpool.defaults.dkim_selector', 'mwl1');
|
$selector = (string) config('mailpool.defaults.dkim_selector', 'mwl1');
|
||||||
$bits = (int) config('mailpool.defaults.dkim_bits', 2048);
|
$bits = (int) config('mailpool.defaults.dkim_bits', 2048);
|
||||||
|
|
||||||
$res = app(\App\Services\DkimService::class)
|
// Service erledigt: Key generieren, DB (upsert) pflegen, Helper ausführen, OpenDKIM reloaden
|
||||||
->generateForDomain($domain, $bits, $selector);
|
app(\App\Services\DkimService::class)->generateForDomain($domain, $bits, $selector);
|
||||||
|
|
||||||
$dk = \App\Models\DkimKey::create([
|
// DNS-Records: aktiven Key aus DB lesen und provisionieren
|
||||||
'domain_id' => $domain->id,
|
$active = $domain->dkimKeys()->where('is_active', true)->latest()->first();
|
||||||
'selector' => $res['selector'],
|
if ($active) {
|
||||||
'private_key_pem' => $res['private_pem'],
|
app(\App\Services\DnsRecordService::class)->provision(
|
||||||
'public_key_txt' => preg_replace('/^v=DKIM1; k=rsa; p=/', '', $res['dns_txt']),
|
$domain,
|
||||||
'is_active' => true,
|
$active->selector,
|
||||||
]);
|
"v=DKIM1; k=rsa; p={$active->public_key_txt}",
|
||||||
|
[
|
||||||
// Helper aufrufen (Pfad aus $res['priv_path']!)
|
'spf_tail' => \App\Models\Setting::get('mailpool.spf_tail', '~all'),
|
||||||
dispatch(new \App\Jobs\InstallDkimKey(
|
'spf_extra' => \App\Models\Setting::get('mailpool.spf_extra', []),
|
||||||
domainId: $domain->id,
|
'dmarc_policy' => \App\Models\Setting::get('mailpool.dmarc_policy', 'none'),
|
||||||
dkimKeyId: $dk->id,
|
'rua' => \App\Models\Setting::get('mailpool.rua', null),
|
||||||
privPath: $res['priv_path'],
|
]
|
||||||
dnsTxtContent: $res['dns_txt'],
|
);
|
||||||
));
|
}
|
||||||
|
|
||||||
// DNS-Records gleich anlegen/aktualisieren
|
|
||||||
app(\App\Services\DnsRecordService::class)->provision(
|
|
||||||
$domain,
|
|
||||||
$dk->selector,
|
|
||||||
"v=DKIM1; k=rsa; p={$dk->public_key_txt}",
|
|
||||||
[
|
|
||||||
'spf_tail' => \App\Models\Setting::get('mailpool.spf_tail', '~all'),
|
|
||||||
'spf_extra' => \App\Models\Setting::get('mailpool.spf_extra', []),
|
|
||||||
'dmarc_policy' => \App\Models\Setting::get('mailpool.dmarc_policy', 'none'),
|
|
||||||
'rua' => \App\Models\Setting::get('mailpool.rua', null),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// public function created(Domain $domain): void
|
// public function created(Domain $domain): void
|
||||||
// {
|
// {
|
||||||
// // Standardwerte aus Config oder .env
|
// if ($domain->is_server) {
|
||||||
// $selector = config('mailwolt.dkim.selector', 'mwl1');
|
// return;
|
||||||
// $bits = (int) config('mailwolt.dkim.bits', 2048);
|
// }
|
||||||
//
|
//
|
||||||
// // Keypair erzeugen
|
// $selector = (string) config('mailpool.defaults.dkim_selector', 'mwl1');
|
||||||
// $res = app(DkimService::class)->generateForDomain(
|
// $bits = (int) config('mailpool.defaults.dkim_bits', 2048);
|
||||||
// domainId: $domain,
|
//
|
||||||
// bits: $bits,
|
// $res = app(\App\Services\DkimService::class)
|
||||||
// selector: $selector
|
// ->generateForDomain($domain, $bits, $selector);
|
||||||
|
//
|
||||||
|
// // DNS-Records gleich anlegen/aktualisieren
|
||||||
|
// app(\App\Services\DnsRecordService::class)->provision(
|
||||||
|
// $domain,
|
||||||
|
// $dk->selector,
|
||||||
|
// "v=DKIM1; k=rsa; p={$dk->public_key_txt}",
|
||||||
|
// [
|
||||||
|
// 'spf_tail' => \App\Models\Setting::get('mailpool.spf_tail', '~all'),
|
||||||
|
// 'spf_extra' => \App\Models\Setting::get('mailpool.spf_extra', []),
|
||||||
|
// 'dmarc_policy' => \App\Models\Setting::get('mailpool.dmarc_policy', 'none'),
|
||||||
|
// 'rua' => \App\Models\Setting::get('mailpool.rua', null),
|
||||||
|
// ]
|
||||||
// );
|
// );
|
||||||
//
|
|
||||||
// // In dkim_keys speichern
|
|
||||||
// $dk = DkimKey::create([
|
|
||||||
// 'domain_id' => $domain->id,
|
|
||||||
// 'selector' => $res['selector'],
|
|
||||||
// 'private_key_pem' => $res['private_pem'],
|
|
||||||
// 'public_key_txt' => preg_replace('/^v=DKIM1; k=rsa; p=/', '', $res['dns_txt']),
|
|
||||||
// 'is_active' => true,
|
|
||||||
// ]);
|
|
||||||
//
|
|
||||||
// // Helper-Job zum Installieren starten
|
|
||||||
// InstallDkimKey::dispatch(
|
|
||||||
// domainId: $domain->id,
|
|
||||||
// dkimKeyId: $dk->id,
|
|
||||||
// privPath: $res['priv_path'],
|
|
||||||
// dnsTxtContent: $res['dns_txt']
|
|
||||||
// )->afterCommit();
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -93,12 +68,28 @@ class DomainObserver
|
||||||
*/
|
*/
|
||||||
public function deleted(Domain $domain): void
|
public function deleted(Domain $domain): void
|
||||||
{
|
{
|
||||||
// Falls SoftDeletes im Spiel, willst du evtl. forceDeleted spiegeln (s.u.)
|
try {
|
||||||
foreach ($domain->dkimKeys()->get() as $dk) {
|
/** @var \App\Services\DkimService $svc */
|
||||||
RemoveDkimKey::dispatch(
|
$svc = app(\App\Services\DkimService::class);
|
||||||
domainId: $domain->id,
|
|
||||||
selector: $dk->selector
|
// Entferne DKIM aus OpenDKIM Config
|
||||||
)->afterCommit();
|
$svc->removeForDomain($domain);
|
||||||
|
|
||||||
|
// Optionale lokale Dateien löschen
|
||||||
|
$path = storage_path("app/private/dkim/{$domain->domain}");
|
||||||
|
if (is_dir($path)) {
|
||||||
|
\Illuminate\Support\Facades\File::deleteDirectory($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload OpenDKIM
|
||||||
|
\Illuminate\Support\Facades\Process::run(['sudo','-n','/usr/bin/systemctl','reload','opendkim']);
|
||||||
|
|
||||||
|
Log::info("Domain deleted + DKIM cleaned", ['domain' => $domain->domain]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error("Domain delete cleanup failed", [
|
||||||
|
'domain' => $domain->domain,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class DkimService
|
||||||
|
|
||||||
$dirKey = $this->dirKeyFor($domain);
|
$dirKey = $this->dirKeyFor($domain);
|
||||||
$selKey = preg_replace('/[^A-Za-z0-9._-]/', '_', substr($selector, 0, 32)); // schlicht & stabil
|
$selKey = preg_replace('/[^A-Za-z0-9._-]/', '_', substr($selector, 0, 32)); // schlicht & stabil
|
||||||
|
|
||||||
// Disk "local" zeigt bei dir auf storage/app/private (siehe Kommentar in deinem Code)
|
// Disk "local" zeigt bei dir auf storage/app/private (siehe Kommentar in deinem Code)
|
||||||
$disk = Storage::disk('local');
|
$disk = Storage::disk('local');
|
||||||
$baseRel = "dkim/{$dirKey}";
|
$baseRel = "dkim/{$dirKey}";
|
||||||
|
|
@ -159,36 +159,58 @@ class DkimService
|
||||||
return $san;
|
return $san;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function safeKey($value, int $max = 64): string
|
public function removeForDomain(Domain|string $domain): void
|
||||||
{
|
{
|
||||||
if (is_object($value)) {
|
$domainName = $domain instanceof Domain ? $domain->domain : $domain;
|
||||||
if (isset($value->id)) $value = $value->id;
|
$keyTable = '/etc/opendkim/KeyTable';
|
||||||
elseif (method_exists($value, 'getKey')) $value = $value->getKey();
|
$signTable = '/etc/opendkim/SigningTable';
|
||||||
else $value = json_encode($value);
|
$keyDir = "/etc/opendkim/keys/{$domainName}";
|
||||||
|
|
||||||
|
// Tabellen bereinigen
|
||||||
|
foreach ([$keyTable, $signTable] as $file) {
|
||||||
|
if (is_file($file)) {
|
||||||
|
$lines = file($file, FILE_IGNORE_NEW_LINES);
|
||||||
|
$filtered = array_filter($lines, fn($l) => !str_contains($l, $domainName));
|
||||||
|
file_put_contents($file, implode(PHP_EOL, $filtered) . PHP_EOL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$raw = (string) $value;
|
|
||||||
$san = preg_replace('/[^A-Za-z0-9._-]/', '_', $raw);
|
// Key-Verzeichnis löschen
|
||||||
if ($san === '' ) $san = 'unknown';
|
if (is_dir($keyDir)) {
|
||||||
if (strlen($san) > $max) {
|
\Illuminate\Support\Facades\File::deleteDirectory($keyDir);
|
||||||
$san = substr($san, 0, $max - 13) . '_' . substr(sha1($raw), 0, 12);
|
|
||||||
}
|
}
|
||||||
return $san;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function extractPublicKeyBase64(string $pem): string
|
// protected function safeKey($value, int $max = 64): string
|
||||||
{
|
// {
|
||||||
// Hole den Body zwischen den Headern (multiline, dotall)
|
// if (is_object($value)) {
|
||||||
if (!preg_match('/^-+BEGIN PUBLIC KEY-+\r?\n(.+?)\r?\n-+END PUBLIC KEY-+\s*$/ms', $pem, $m)) {
|
// if (isset($value->id)) $value = $value->id;
|
||||||
throw new \RuntimeException('DKIM: Ungültiges Public-Key-PEM (Header/Footers nicht gefunden).');
|
// elseif (method_exists($value, 'getKey')) $value = $value->getKey();
|
||||||
}
|
// else $value = json_encode($value);
|
||||||
|
// }
|
||||||
// Whitespace entfernen → reines Base64
|
// $raw = (string) $value;
|
||||||
$b64 = preg_replace('/\s+/', '', $m[1]);
|
// $san = preg_replace('/[^A-Za-z0-9._-]/', '_', $raw);
|
||||||
|
// if ($san === '' ) $san = 'unknown';
|
||||||
if ($b64 === '' || base64_decode($b64, true) === false) {
|
// if (strlen($san) > $max) {
|
||||||
throw new \RuntimeException('DKIM: Public Key Base64 ist leer/ungültig.');
|
// $san = substr($san, 0, $max - 13) . '_' . substr(sha1($raw), 0, 12);
|
||||||
}
|
// }
|
||||||
|
// return $san;
|
||||||
return $b64;
|
// }
|
||||||
}
|
//
|
||||||
|
// protected static function extractPublicKeyBase64(string $pem): string
|
||||||
|
// {
|
||||||
|
// // Hole den Body zwischen den Headern (multiline, dotall)
|
||||||
|
// if (!preg_match('/^-+BEGIN PUBLIC KEY-+\r?\n(.+?)\r?\n-+END PUBLIC KEY-+\s*$/ms', $pem, $m)) {
|
||||||
|
// throw new \RuntimeException('DKIM: Ungültiges Public-Key-PEM (Header/Footers nicht gefunden).');
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Whitespace entfernen → reines Base64
|
||||||
|
// $b64 = preg_replace('/\s+/', '', $m[1]);
|
||||||
|
//
|
||||||
|
// if ($b64 === '' || base64_decode($b64, true) === false) {
|
||||||
|
// throw new \RuntimeException('DKIM: Public Key Base64 ist leer/ungültig.');
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return $b64;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,32 @@
|
||||||
<div class="w-full #min-h-[86vh] grid place-items-center px-4
|
<div class="w-full #min-h-[86vh] grid place-items-center
|
||||||
bg-[radial-gradient(1200px_600px_at_10%_-10%,rgba(59,130,246,.08),transparent),
|
bg-[radial-gradient(1200px_600px_at_10%_-10%,rgba(59,130,246,.08),transparent),
|
||||||
radial-gradient(900px_500px_at_90%_0%,rgba(99,102,241,.06),transparent)]">
|
radial-gradient(900px_500px_at_90%_0%,rgba(99,102,241,.06),transparent)]">
|
||||||
|
|
||||||
|
{{-- Banner (dismissbar) --}}
|
||||||
|
@if($showBanner && $banner)
|
||||||
|
<div class="max-w-[520px] mb-5 rounded-2xl border border-emerald-400/30 text-emerald-300 bg-emerald-500/10 p-4 #md:p-5 shadow backdrop-blur"
|
||||||
|
role="status" aria-live="polite">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="shrink-0 mt-0.5">
|
||||||
|
<i class="ph ph-check-circle text-emerald-100 text-xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="font-semibold text-emerald-100">
|
||||||
|
{{ $banner['title'] ?? 'Erfolgreich registriert' }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-0.5 text-sm text-emerald-200">
|
||||||
|
{{ $banner['message'] ?? 'Dein Konto ist bereit. Melde dich jetzt an.' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="nx-card w-full max-w-[520px]">
|
<div class="nx-card w-full max-w-[520px]">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div class="flex items-center justify-between mb-5">
|
||||||
<span class="nx-chip">Erster Login</span>
|
<span class="nx-chip">Erster Login</span>
|
||||||
<i class="ph ph-lock-simple text-white/60"></i>
|
<i class="ph ph-lock-simple text-white/60"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="nx-subtle mb-7">
|
|
||||||
Melde dich mit dem einmaligen Bootstrap-Konto an, um den Setup-Wizard zu starten.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{{-- globale Fehlermeldung --}}
|
{{-- globale Fehlermeldung --}}
|
||||||
@if($error)
|
@if($error)
|
||||||
<div class="nx-alert mb-6">
|
<div class="nx-alert mb-6">
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
@if($systemDomain)
|
@if($systemDomain)
|
||||||
<div class="rounded-xl border border-white/10 bg-white/[0.04] px-3 py-3">
|
<div class="rounded-xl border border-white/10 bg-white/[0.04] px-3 py-3">
|
||||||
<div class="grid grid-cols-1 #md:grid-cols-4 items-center gap-3">
|
<div class="grid grid-cols-1 md:grid-cols-4 items-center gap-3">
|
||||||
<div class=" flex items-center gap-1 text-white/90 font-medium truncate">
|
<div class=" flex items-center gap-1 text-white/90 font-medium truncate">
|
||||||
<span class="relative flex size-2.5 mx-1">
|
<span class="relative flex size-2.5 mx-1">
|
||||||
@if($systemDomain->is_active)
|
@if($systemDomain->is_active)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
<div class="rounded-xl border border-white/10 bg-white/[0.04] #rounded-xl #border #border-slate-700/50 #bg-slate-900/60">
|
<div class="rounded-xl border border-white/10 bg-white/[0.04] #rounded-xl #border #border-slate-700/50 #bg-slate-900/60">
|
||||||
<div class="flex items-center justify-between px-4 py-2 text-[12px]">
|
<div class="flex items-center justify-between px-4 py-2 text-[12px]">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="px-2 py-0.5 rounded bg-slate-800/80 text-slate-200 {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">{{ $r['type'] }}</span>
|
<span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">{{ $r['type'] }}</span>
|
||||||
<span class="text-slate-200">{{ $r['name'] }}</span>
|
<span class="text-slate-200">{{ $r['name'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 text-slate-300/70">
|
<div class="flex items-center gap-2 text-slate-300/70">
|
||||||
|
|
@ -52,6 +52,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
|
@foreach ($optional as $r)
|
||||||
|
<div class="rounded-xl border border-white/10 bg-white/[0.04] #rounded-xl #border #border-slate-700/50 #bg-slate-900/60">
|
||||||
|
<div class="flex items-center justify-between px-4 py-2 text-[12px]">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">{{ $r['type'] }}</span>
|
||||||
|
<span class="text-slate-200">{{ $r['name'] }}</span>
|
||||||
|
</div>
|
||||||
|
{{ $recordColors[$r['type']] }}
|
||||||
|
<div class="flex items-center gap-2 text-slate-300/70">
|
||||||
|
<x-button.copy-btn :text="$r['value']" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 pb-3">
|
||||||
|
<pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 text-sm opacity-70 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>
|
||||||
|
@if(!empty($r['helpUrl']))
|
||||||
|
<a href="{{ $r['helpUrl'] }}" target="_blank" rel="noopener"
|
||||||
|
class="mt-2 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200">
|
||||||
|
<i class="ph ph-arrow-square-out"></i>{{ $r['helpLabel'] }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,10 @@ Route::get('/login', [LoginController::class, 'show'])->name('login');
|
||||||
Route::get('/signup', [\App\Http\Controllers\Auth\SignUpController::class, 'show' ])->middleware('signup.open')->name('signup');
|
Route::get('/signup', [\App\Http\Controllers\Auth\SignUpController::class, 'show' ])->middleware('signup.open')->name('signup');
|
||||||
Route::post('/logout', [\App\Http\Controllers\Auth\LoginController::class, 'logout'])->name('logout');
|
Route::post('/logout', [\App\Http\Controllers\Auth\LoginController::class, 'logout'])->name('logout');
|
||||||
|
|
||||||
Route::middleware(['auth', 'ensure.setup'])->group(function () {
|
//Route::middleware(['auth', 'ensure.setup'])->group(function () {
|
||||||
// Route::get('/dashboard', Dashboard::class)->name('dashboard');
|
//// Route::get('/dashboard', Dashboard::class)->name('dashboard');
|
||||||
Route::get('/setup', [SetupWizard::class, 'show'])->name('setup.wizard');
|
// Route::get('/setup', [SetupWizard::class, 'show'])->name('setup.wizard');
|
||||||
});
|
//});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue