parent
a8e7aedadf
commit
529979f078
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AuthenticatedMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (!Auth::check()) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['message' => 'Unauthenticated'], 401);
|
||||
}
|
||||
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class GuestOnlyMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (Auth::check()) {
|
||||
// Eingeloggt → z. B. Dashboard weiterleiten
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@ class HealthCard extends Component
|
|||
public ?int $ramPercent = null;
|
||||
public ?string $updatedAtHuman = null;
|
||||
|
||||
public $guardOk = [];
|
||||
public function mount(): void
|
||||
{
|
||||
$this->loadData();
|
||||
|
|
@ -179,19 +180,52 @@ class HealthCard extends Component
|
|||
'127.0.0.1:8080' => ['label' => 'Reverb', 'hint' => 'WebSocket'],
|
||||
];
|
||||
|
||||
$this->servicesCompact = collect($this->services)
|
||||
->map(function ($srv) use ($nameMap) {
|
||||
$key = (string)($srv['name'] ?? '');
|
||||
$ok = (bool) ($srv['ok'] ?? false);
|
||||
$label = $nameMap[$key]['label'] ?? ucfirst($key);
|
||||
$hint = $nameMap[$key]['hint'] ?? (str_contains($key, ':') ? $key : '');
|
||||
// $nameMap = [
|
||||
// // --- Mail ---
|
||||
// 'postfix' => ['label' => 'Postfix', 'hint' => 'MTA / Versand'],
|
||||
// 'dovecot' => ['label' => 'Dovecot', 'hint' => 'IMAP / POP3'],
|
||||
// 'rspamd' => ['label' => 'Rspamd', 'hint' => 'Spamfilter'],
|
||||
// 'clamav' => ['label' => 'ClamAV', 'hint' => 'Virenscanner'],
|
||||
//
|
||||
// // --- Daten & Cache ---
|
||||
// 'db' => ['label' => 'Datenbank', 'hint' => 'MySQL / MariaDB'],
|
||||
// '127.0.0.1:6379' => ['label' => 'Redis', 'hint' => 'Cache / Queue'],
|
||||
//
|
||||
// // --- Web / PHP ---
|
||||
// 'php8.2-fpm' => ['label' => 'PHP-FPM', 'hint' => 'PHP Runtime'],
|
||||
// 'nginx' => ['label' => 'Nginx', 'hint' => 'Webserver'],
|
||||
//
|
||||
// // --- MailWolt spezifisch ---
|
||||
// 'mailwolt-queue' => ['label' => 'MailWolt Queue', 'hint' => 'Job Worker'],
|
||||
// 'mailwolt-schedule'=> ['label' => 'MailWolt Schedule','hint' => 'Task Scheduler'],
|
||||
//
|
||||
// // --- Sonstige Infrastruktur ---
|
||||
// 'fail2ban' => ['label' => 'Fail2Ban', 'hint' => 'SSH / Mail Protection'],
|
||||
// 'systemd-journald'=> ['label' => 'System Logs', 'hint' => 'Logging / Journal'],
|
||||
//
|
||||
// // --- WebSocket & Echtzeit ---
|
||||
// '127.0.0.1:8080' => ['label' => 'Reverb', 'hint' => 'WebSocket Server'],
|
||||
// ];
|
||||
|
||||
$existing = collect($this->services)->keyBy('name');
|
||||
|
||||
$this->servicesCompact = collect($nameMap)
|
||||
->map(function ($meta, $key) use ($existing) {
|
||||
$srv = $existing->get($key, []);
|
||||
$ok = (bool)($srv['ok'] ?? false);
|
||||
|
||||
return [
|
||||
'label' => $label,
|
||||
'hint' => $hint,
|
||||
'label' => $meta['label'],
|
||||
'hint' => $meta['hint'],
|
||||
'ok' => $ok,
|
||||
|
||||
// Punktfarbe
|
||||
'dotClass' => $ok ? 'bg-emerald-400' : 'bg-rose-400',
|
||||
'pillText' => $ok ? 'ok' : 'down',
|
||||
|
||||
// ✅ Bessere Status-Texte
|
||||
'pillText' => $ok ? 'Aktiv' : 'Offline',
|
||||
|
||||
// Farbe für Pill
|
||||
'pillClass' => $ok
|
||||
? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10'
|
||||
: 'text-rose-300 border-rose-400/30 bg-rose-500/10',
|
||||
|
|
@ -199,6 +233,31 @@ class HealthCard extends Component
|
|||
})
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$this->guardOk = collect($this->services)->every(
|
||||
fn($s) => (bool)($s['ok'] ?? false)
|
||||
);
|
||||
// $this->servicesCompact = collect($this->services)
|
||||
// ->map(function ($srv) use ($nameMap) {
|
||||
// $key = (string)($srv['name'] ?? '');
|
||||
// $ok = (bool) ($srv['ok'] ?? false);
|
||||
// $label = $nameMap[$key]['label'] ?? ucfirst($key);
|
||||
// $hint = $nameMap[$key]['hint'] ?? (str_contains($key, ':') ? $key : '');
|
||||
//
|
||||
// var_dump($srv);
|
||||
// return [
|
||||
// 'label' => $label,
|
||||
// 'hint' => $hint,
|
||||
// 'ok' => $ok,
|
||||
// 'dotClass' => $ok ? 'bg-emerald-400' : 'bg-rose-400',
|
||||
// 'pillText' => $ok ? 'Läuft' : 'Down',
|
||||
// 'pillClass' => $ok
|
||||
// ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10'
|
||||
// : 'text-rose-300 border-rose-400/30 bg-rose-500/10',
|
||||
// ];
|
||||
// })
|
||||
// ->values()
|
||||
// ->all();
|
||||
}
|
||||
|
||||
protected function decorateDisk(): void
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class TopBar extends Component
|
|||
public function mount(): void
|
||||
{
|
||||
// Domains + Zertifikate (passe an deinen Storage an)
|
||||
$this->domainsCount = Domain::count(); // oder aus Cache/Repo
|
||||
$this->domainsCount = Domain::where('is_server', false)->where('is_system', false)->count(); // oder aus Cache/Repo
|
||||
|
||||
// Beispiel: Domain::select('cert_expires_at','cert_issuer')...
|
||||
// -> hier simple Annahme: im Cache 'domains:certs' liegt [{domain, days_left, type}]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,324 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Livewire\Ui\System;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class UpdateCard extends Component
|
||||
{
|
||||
public ?string $current = null; // installierte Version
|
||||
public ?string $latest = null; // verfügbare Version (oder null)
|
||||
public string $state = 'idle'; // idle | running
|
||||
|
||||
// UI-Textausgabe nach einer Prüfung / Aktion
|
||||
public ?string $message = null; // z.B. "Du bist auf dem neuesten Stand (v1.0.16)"
|
||||
public ?bool $messagePositive = null; // true = grün, false = neutral/weiß
|
||||
|
||||
// low-level (falls du sie später brauchst)
|
||||
public bool $running = false; // aus /var/lib/mailwolt/update/state
|
||||
public ?int $rc = null;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->current = $this->readCurrentVersion();
|
||||
$this->latest = Cache::get('mailwolt.update_available');
|
||||
$this->refreshLowLevelState();
|
||||
|
||||
// Starttext, falls nichts geprüft wurde
|
||||
if ($this->message === null) {
|
||||
$this->message = $this->latest && $this->hasUpdate()
|
||||
? "Neue Version verfügbar: {$this->latest}"
|
||||
: ($this->current ? "Du bist auf dem neuesten Stand ({$this->current})" : "Status unbekannt");
|
||||
$this->messagePositive = !$this->hasUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.ui.system.update-card');
|
||||
}
|
||||
|
||||
/**
|
||||
* „Erneut prüfen“ – ohne Toast:
|
||||
* - Progress anzeigen
|
||||
* - Check-Command laufen lassen
|
||||
* - Message in der Box aktualisieren
|
||||
*/
|
||||
public function refreshState(): void
|
||||
{
|
||||
$this->state = 'running';
|
||||
$this->message = 'Prüfe auf Updates …';
|
||||
$this->messagePositive = null;
|
||||
|
||||
try {
|
||||
// Passe den Namen hier an dein tatsächliches Command an:
|
||||
Artisan::call('mailwolt:check-updates');
|
||||
} catch (\Throwable $e) {
|
||||
// weich fallen
|
||||
}
|
||||
|
||||
// Daten neu einlesen
|
||||
$this->current = $this->readCurrentVersion();
|
||||
$this->latest = Cache::get('mailwolt.update_available');
|
||||
|
||||
if ($this->hasUpdate()) {
|
||||
$this->message = "Neue Version verfügbar: {$this->latest}";
|
||||
$this->messagePositive = false; // neutral
|
||||
} else {
|
||||
$cur = $this->current ?: '–';
|
||||
$this->message = "Du bist auf dem neuesten Stand ({$cur})";
|
||||
$this->messagePositive = true; // grün
|
||||
}
|
||||
|
||||
$this->refreshLowLevelState();
|
||||
$this->state = 'idle';
|
||||
}
|
||||
|
||||
/**
|
||||
* „Jetzt aktualisieren“ – ohne Toast:
|
||||
* - Hinweis sofort aus Cache entfernen (Badge weg)
|
||||
* - Update-Wrapper starten
|
||||
* - Running + Text in der Box anzeigen
|
||||
*/
|
||||
public function runUpdate(): void
|
||||
{
|
||||
Cache::forget('mailwolt.update_available');
|
||||
|
||||
@shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &');
|
||||
|
||||
$this->latest = null;
|
||||
$this->state = 'running';
|
||||
$this->running = true;
|
||||
$this->message = 'Update läuft …';
|
||||
$this->messagePositive = null;
|
||||
}
|
||||
|
||||
/** --------------------- helpers --------------------- */
|
||||
|
||||
protected function hasUpdate(): bool
|
||||
{
|
||||
if (!$this->latest) return false;
|
||||
$cur = $this->current ?: '0.0.0';
|
||||
return version_compare($this->latest, $cur, '>');
|
||||
}
|
||||
|
||||
protected function refreshLowLevelState(): void
|
||||
{
|
||||
$state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: '');
|
||||
$this->running = ($state === 'running');
|
||||
|
||||
$rcRaw = @trim(@file_get_contents('/var/lib/mailwolt/update/rc') ?: '');
|
||||
$this->rc = is_numeric($rcRaw) ? (int)$rcRaw : null;
|
||||
}
|
||||
|
||||
protected function readCurrentVersion(): ?string
|
||||
{
|
||||
// bevorzugt /etc/mailwolt/build.info (wird im Installer/Updater gepflegt)
|
||||
$build = @file_get_contents('/etc/mailwolt/build.info');
|
||||
if ($build) {
|
||||
foreach (preg_split('/\R+/', $build) as $line) {
|
||||
if (str_starts_with($line, 'version=')) {
|
||||
$v = trim(substr($line, 8));
|
||||
if ($v !== '') return $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
$v = config('app.version');
|
||||
return $v !== '' ? $v : null;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//namespace App\Livewire\Ui\System;
|
||||
//
|
||||
//use Livewire\Component;
|
||||
//use Illuminate\Support\Facades\Artisan;
|
||||
//use Illuminate\Support\Facades\Cache;
|
||||
//
|
||||
//class UpdateCard extends Component
|
||||
//{
|
||||
// public ?string $current = null; // z.B. v1.0.16 (aktuell installiert)
|
||||
// public ?string $latest = null; // z.B. v1.0.17 (verfügbar) oder null
|
||||
// public string $state = 'idle'; // idle | running
|
||||
//
|
||||
// // optional: Low-level-Infos, falls du sie irgendwo anders brauchst
|
||||
// public bool $running = false; // Wrapper-/Updater läuft gerade (aus State-Datei)
|
||||
// public string $log = '';
|
||||
// public ?int $rc = null;
|
||||
//
|
||||
// public function mount(): void
|
||||
// {
|
||||
// $this->current = $this->readCurrentVersion();
|
||||
// $this->latest = Cache::get('mailwolt.update_available');
|
||||
// $this->refreshLowLevelState(); // liest /var/lib/mailwolt/update/*
|
||||
// }
|
||||
//
|
||||
// public function render()
|
||||
// {
|
||||
// return view('livewire.ui.system.update-card');
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * „Erneut prüfen“:
|
||||
// * - zeigt Running-Progress
|
||||
// * - ruft den Checker (dein bestehendes Artisan-Command)
|
||||
// * - aktualisiert $latest / $current
|
||||
// * - dispatcht passenden Toast
|
||||
// */
|
||||
// public function refreshState(): void
|
||||
// {
|
||||
// $this->state = 'running';
|
||||
//
|
||||
// // Falls vorhanden: dein Command, der cache('mailwolt.update_available') setzt
|
||||
// // -> Namen ggf. anpassen (du hattest z.B. mailwolt:check-updates erwähnt)
|
||||
// try {
|
||||
// Artisan::call('mailwolt:check-updates');
|
||||
// } catch (\Throwable $e) {
|
||||
// // Wenn das Command (noch) nicht existiert, nicht crashen – state einfach zurücksetzen
|
||||
// }
|
||||
//
|
||||
// // App-Status neu einlesen
|
||||
// $this->current = $this->readCurrentVersion();
|
||||
// $this->latest = Cache::get('mailwolt.update_available');
|
||||
//
|
||||
// $hasUpdate = $this->hasUpdate();
|
||||
//
|
||||
// // Toast ausspielen
|
||||
// $this->dispatch('toast',
|
||||
// type: $hasUpdate ? 'info' : 'done',
|
||||
// badge: 'Update',
|
||||
// title: $hasUpdate ? 'Neue Version verfügbar' : 'Alles aktuell',
|
||||
// text: $hasUpdate
|
||||
// ? "Verfügbar: {$this->latest} – installiert: {$this->current}"
|
||||
// : "Du bist auf dem neuesten Stand ({$this->current}).",
|
||||
// duration: 6000
|
||||
// );
|
||||
//
|
||||
// // Low-level-State (optional) und UI-State zurücksetzen
|
||||
// $this->refreshLowLevelState();
|
||||
// $this->state = 'idle';
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * „Jetzt aktualisieren“:
|
||||
// * - blendet die verfügbare-Version sofort aus
|
||||
// * - startet den Root-Wrapper im Hintergrund
|
||||
// * - zeigt einen Toast „Update gestartet“
|
||||
// */
|
||||
// public function runUpdate(): void
|
||||
// {
|
||||
// Cache::forget('mailwolt.update_available'); // Badge sofort weg
|
||||
// @shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &');
|
||||
//
|
||||
// $this->state = 'running';
|
||||
// $this->running = true;
|
||||
//
|
||||
// $this->dispatch('toast',
|
||||
// type: 'info',
|
||||
// badge: 'Update',
|
||||
// title: 'Update gestartet',
|
||||
// text: 'Das System wird aktualisiert …',
|
||||
// duration: 6000
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// /** ---- Helpers ------------------------------------------------------- */
|
||||
//
|
||||
// protected function hasUpdate(): bool
|
||||
// {
|
||||
// if (!$this->latest) return false;
|
||||
// $cur = $this->current ?: '0.0.0';
|
||||
// return version_compare($this->latest, $cur, '>');
|
||||
// }
|
||||
//
|
||||
// protected function refreshLowLevelState(): void
|
||||
// {
|
||||
// $state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: '');
|
||||
// $this->running = ($state === 'running');
|
||||
//
|
||||
// $rcRaw = @trim(@file_get_contents('/var/lib/mailwolt/update/rc') ?: '');
|
||||
// $this->rc = is_numeric($rcRaw) ? (int)$rcRaw : null;
|
||||
//
|
||||
// // Log bewusst NICHT angezeigt, aber verfügbar, falls du es später brauchst
|
||||
// $this->log = @shell_exec('tail -n 200 /var/log/mailwolt-update.log 2>/dev/null') ?? '';
|
||||
// }
|
||||
//
|
||||
// protected function readCurrentVersion(): ?string
|
||||
// {
|
||||
// // bevorzugt build.info, sonst config('app.version')
|
||||
// $build = @file_get_contents('/etc/mailwolt/build.info');
|
||||
// if ($build) {
|
||||
// foreach (preg_split('/\R+/', $build) as $line) {
|
||||
// if (str_starts_with($line, 'version=')) {
|
||||
// $v = trim(substr($line, 8));
|
||||
// if ($v !== '') return $v;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// $v = config('app.version');
|
||||
// return $v !== '' ? $v : null;
|
||||
// }
|
||||
//}
|
||||
////
|
||||
////namespace App\Livewire\Ui\System;
|
||||
////
|
||||
////use Livewire\Component;
|
||||
////use Illuminate\Support\Facades\Artisan;
|
||||
////use Illuminate\Support\Facades\Cache;
|
||||
////
|
||||
////class UpdateCard extends Component
|
||||
////{
|
||||
//// public ?string $current = null;
|
||||
//// public ?string $latest = null;
|
||||
//// public string $state = 'idle'; // idle|running
|
||||
////
|
||||
//// public bool $running = false;
|
||||
//// public string $log = '';
|
||||
//// public ?int $rc = null;
|
||||
////
|
||||
//// public function mount(): void
|
||||
//// {
|
||||
////// $this->refreshState();
|
||||
//// $this->latest = cache('mailwolt.update_available');
|
||||
////
|
||||
//// }
|
||||
////
|
||||
//// public function render()
|
||||
//// {
|
||||
//// return view('livewire.ui.system.update-card');
|
||||
//// }
|
||||
////
|
||||
//// public function refreshState(): void
|
||||
//// {
|
||||
//// $state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: '');
|
||||
//// $this->running = ($state === 'running');
|
||||
////
|
||||
//// $rcRaw = @trim(@file_get_contents('/var/lib/mailwolt/update/rc') ?: '');
|
||||
//// $this->rc = is_numeric($rcRaw) ? (int)$rcRaw : null;
|
||||
////
|
||||
//// // letzte 200 Zeilen Log
|
||||
//// $this->log = @shell_exec('tail -n 200 /var/log/mailwolt-update.log 2>/dev/null') ?? '';
|
||||
//// }
|
||||
////
|
||||
//// public function runUpdate(): void
|
||||
//// {
|
||||
//// // Hinweis „Update verfügbar“ ausblenden
|
||||
//// cache()->forget('mailwolt.update_available');
|
||||
////
|
||||
//// // Update im Hintergrund starten
|
||||
//// @shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &');
|
||||
////
|
||||
//// $this->running = true;
|
||||
//// $this->dispatch('toast',
|
||||
//// type: 'info',
|
||||
//// badge: 'Update',
|
||||
//// title: 'Update gestartet',
|
||||
//// text: 'Das System wird aktualisiert …',
|
||||
//// duration: 6000
|
||||
//// );
|
||||
//// }
|
||||
////}
|
||||
|
|
@ -23,13 +23,8 @@ class AppServiceProvider extends ServiceProvider
|
|||
*/
|
||||
public function boot(\App\Support\SettingsRepository $settings): void
|
||||
{
|
||||
|
||||
Domain::observe(DomainObserver::class);
|
||||
|
||||
// $ver = trim(@file_get_contents(base_path('VERSION'))) ?: 'dev';
|
||||
// config(['app.version' => $ver]);
|
||||
|
||||
|
||||
config(['app.version' => trim(@file_get_contents('/var/lib/mailwolt/version')) ?: 'dev']);
|
||||
if (file_exists(base_path('.git/HEAD'))) {
|
||||
$ref = trim(file_get_contents(base_path('.git/HEAD')));
|
||||
|
|
@ -39,6 +34,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
} else {
|
||||
$commit = $ref;
|
||||
}
|
||||
|
||||
config(['app.git_commit' => substr($commit ?? '', 0, 7)]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Support\BuildMeta;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class BuildMetaServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(BuildMeta::class, fn () => BuildMeta::detect());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$meta = $this->app->make(BuildMeta::class);
|
||||
|
||||
// zentral verfügbar
|
||||
config([
|
||||
'app.version' => $meta->version,
|
||||
'app.git_rev' => $meta->rev,
|
||||
'app.git_short' => $meta->short,
|
||||
'app.build_time' => $meta->updated,
|
||||
]);
|
||||
|
||||
// optional: in allen Views als $build
|
||||
View::share('build', $meta);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final class BuildMeta
|
||||
{
|
||||
public string $version = 'dev';
|
||||
public string $rev = '';
|
||||
public string $short = '';
|
||||
public ?string $updated = null;
|
||||
|
||||
public static function detect(
|
||||
string $buildFile = '/etc/mailwolt/build.info',
|
||||
string $basePath = null
|
||||
): self {
|
||||
$m = new self();
|
||||
$basePath ??= base_path();
|
||||
|
||||
// 1) /etc/mailwolt/build.info (vom Updater)
|
||||
if (is_file($buildFile) && is_readable($buildFile)) {
|
||||
$lines = @file($buildFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
foreach ($lines as $line) {
|
||||
if (!str_contains($line, '=')) continue;
|
||||
[$k, $v] = explode('=', $line, 2);
|
||||
$k = trim($k); $v = trim($v);
|
||||
if ($k === 'version') $m->version = $v;
|
||||
if ($k === 'rev') $m->rev = $v;
|
||||
if ($k === 'updated') $m->updated = $v;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Fallback: .env APP_VERSION
|
||||
if ($m->version === 'dev' && ($envVer = env('APP_VERSION'))) {
|
||||
$m->version = trim($envVer);
|
||||
}
|
||||
|
||||
// 3) Fallback: Git (ohne "-dirty")
|
||||
if ($m->rev === '' || $m->version === 'dev') {
|
||||
$head = $basePath.'/.git/HEAD';
|
||||
if (is_file($head)) {
|
||||
$ref = trim((string)@file_get_contents($head));
|
||||
if (Str::startsWith($ref, 'ref:')) {
|
||||
$refFile = $basePath.'/.git/'.substr($ref, 5);
|
||||
$commit = @file_get_contents($refFile);
|
||||
} else {
|
||||
$commit = $ref;
|
||||
}
|
||||
$m->rev = $m->rev ?: trim((string)$commit);
|
||||
|
||||
if ($m->version === 'dev') {
|
||||
$tag = @shell_exec('git -C '.escapeshellarg($basePath).' describe --tags --abbrev=0 2>/dev/null');
|
||||
$m->version = $tag ? trim($tag) : 'dev';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sauber: "-dirty" entfernen
|
||||
$m->version = preg_replace('/-dirty$/', '', $m->version);
|
||||
|
||||
// Kurz-Commit
|
||||
$m->short = $m->rev ? substr($m->rev, 0, 7) : '';
|
||||
|
||||
return $m;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'version' => $this->version,
|
||||
'rev' => $this->rev,
|
||||
'short' => $this->short,
|
||||
'updated' => $this->updated,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,8 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
$middleware->alias([
|
||||
'ensure.setup' => \App\Http\Middleware\EnsureSetupCompleted::class,
|
||||
'signup.open' => \App\Http\Middleware\SignupOpen::class,
|
||||
'auth.user' => \App\Http\Middleware\AuthenticatedMiddleware::class,
|
||||
'guest.only' => \App\Http\Middleware\GuestOnlyMiddleware::class,
|
||||
|
||||
]);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@
|
|||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\BuildMetaServiceProvider::class,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
'name' => env('APP_NAME', 'MailWolt'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
@ -123,6 +123,8 @@ return [
|
|||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||
],
|
||||
|
||||
'version' => env('APP_VERSION', '1.0.0'),
|
||||
'git_commit' => env('APP_GIT_COMMIT', ''),
|
||||
'version' => env('APP_VERSION', 'dev'),
|
||||
'git_rev' => null,
|
||||
'git_short' => null,
|
||||
'build_time' => null,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -18,25 +18,25 @@ return [
|
|||
[
|
||||
'title' => 'Security',
|
||||
'icon' => 'icons.icon-security',
|
||||
'route' => 'logout',
|
||||
'route' => 'ui.logout',
|
||||
'roles' => ['super_admin', 'admin', 'employee', 'user'],
|
||||
],
|
||||
[
|
||||
'title' => 'Team',
|
||||
'icon' => 'icons.icon-team',
|
||||
'route' => 'logout',
|
||||
'route' => 'ui.logout',
|
||||
'roles' => ['super_admin', 'admin', 'employee', 'user'],
|
||||
],
|
||||
[
|
||||
'title' => 'Settings',
|
||||
'icon' => 'icons.icon-settings',
|
||||
'route' => 'logout',
|
||||
'route' => 'ui.logout',
|
||||
'roles' => ['super_admin', 'admin', 'employee', 'user'],
|
||||
],
|
||||
[
|
||||
'title' => 'Logout',
|
||||
'icon' => 'icons.icon-logout',
|
||||
'route' => 'logout',
|
||||
'route' => 'ui.logout',
|
||||
'roles' => ['super_admin', 'admin', 'employee', 'user'],
|
||||
],
|
||||
]
|
||||
|
|
|
|||
|
|
@ -32,6 +32,14 @@
|
|||
|
||||
}
|
||||
|
||||
@keyframes pulse-slow {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 0.8; }
|
||||
}
|
||||
.animate-pulse-slow {
|
||||
animation: pulse-slow 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.safe-pads {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
|
|
|
|||
|
|
@ -1,36 +1,13 @@
|
|||
{{--resources/views/components/partials/header.blade.php--}}
|
||||
<div
|
||||
class="#sticky top-0 w-full border-b hr rounded-lg">
|
||||
|
||||
class="#sticky top-0 w-full border-b hr #rounded-lg max-w-9xl mx-auto">
|
||||
<header id="header" class="header w-full rounded-r-2xl rounded-l-none">
|
||||
<nav class="flex h-[71px] #h-[74px] items-center justify-between #p-3">
|
||||
<div class="#flex-1 md:w-3/5 w-full">
|
||||
<div class="relative flex items-center gap-5">
|
||||
<button class="sidebar-toggle translate-0 right-5 block sm:hidden text-white/60 hover:text-white text-2xl">
|
||||
<button class="sidebar-toggle translate-0 right-5 block s#m:hidden text-white/60 hover:text-white text-2xl">
|
||||
<i class="ph ph-list"></i>
|
||||
</button>
|
||||
{{-- @if ($latest = cache('mailwolt.update_available'))--}}
|
||||
{{-- <div class="bg-blue-900/40 text-blue-100 p-4 rounded-xl border border-blue-800">--}}
|
||||
{{-- <div class="flex justify-between items-center">--}}
|
||||
{{-- <div>--}}
|
||||
{{-- <strong>Neue Version verfügbar:</strong> {{ $latest }}--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <button wire:click="runUpdate"--}}
|
||||
{{-- class="bg-blue-600 hover:bg-blue-500 text-white px-3 py-1 rounded">--}}
|
||||
{{-- Jetzt aktualisieren--}}
|
||||
{{-- </button>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endif--}}
|
||||
{{-- <button id="sidebar-toggle-btn"--}}
|
||||
{{-- class="action-button sidebar-toggle flex items-center justify-center p-1.5 shadow-2xl rounded">--}}
|
||||
{{-- <svg class="size-5 group-[.expanded]/side:rotate-180"--}}
|
||||
{{-- fill="none" stroke="currentColor" stroke-width="1.5"--}}
|
||||
{{-- viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">--}}
|
||||
{{-- <path stroke-linecap="round" stroke-linejoin="round"--}}
|
||||
{{-- d="M9 5l7 7-7 7"></path>--}}
|
||||
{{-- </svg>--}}
|
||||
{{-- </button>--}}
|
||||
<div class="flex items-center gap-3">
|
||||
<h1 class="font-bold text-2xl">@yield('header_title')</h1>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -83,20 +83,19 @@
|
|||
</nav>
|
||||
|
||||
{{-- Footer Toggle (Desktop/Tablet) --}}
|
||||
<div class="p-3 border-t hr flex justify-between items-center">
|
||||
{{-- Version (z.B. aus config/app.php 'version' oder env) --}}
|
||||
<div class="sidebar-label text-[10px] text-slate-300/70">
|
||||
<span class="text-xs text-white/40">v{{ config('app.version') }}</span>
|
||||
<div class="p-5 border-t hr flex justify-center items-center">
|
||||
<div class="sidebar-label text-[10px] text-slate-300/70 whitespace-nowrap">
|
||||
<span class="text-xs text-white/40">{{ config('app.name') }} | v{{ config('app.version') }}</span>
|
||||
</div>
|
||||
|
||||
<button type="button"
|
||||
class="action-button sidebar-toggle flex items-center justify-center p-2 shadow-2xl rounded"
|
||||
aria-label="Sidebar ein/ausklappen">
|
||||
<svg class="size-6" fill="none" stroke="currentColor" stroke-width="1.5"
|
||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
{{-- <button type="button"--}}
|
||||
{{-- class="action-button sidebar-toggle flex items-center justify-center p-2 shadow-2xl rounded"--}}
|
||||
{{-- aria-label="Sidebar ein/ausklappen">--}}
|
||||
{{-- <svg class="size-6" fill="none" stroke="currentColor" stroke-width="1.5"--}}
|
||||
{{-- viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">--}}
|
||||
{{-- <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"></path>--}}
|
||||
{{-- </svg>--}}
|
||||
{{-- </button>--}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
<div class="glass-card p-4 flex flex-col justify-between min-h-[140px]">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<div
|
||||
class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<i class="ph ph-cpu text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] tracking-wide uppercase text-white/70">CPU</span>
|
||||
</div>
|
||||
|
|
@ -31,7 +32,8 @@
|
|||
<div class="glass-card p-4 flex flex-col justify-between min-h-[140px]">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<div
|
||||
class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<i class="ph ph-memory text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] tracking-wide uppercase text-white/70">RAM</span>
|
||||
</div>
|
||||
|
|
@ -54,7 +56,8 @@
|
|||
<div class="glass-card p-4 flex flex-col justify-between min-h-[140px]">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<div
|
||||
class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<i class="ph ph-activity text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] tracking-wide uppercase text-white/70">Load</span>
|
||||
</div>
|
||||
|
|
@ -78,7 +81,8 @@
|
|||
<div class="glass-card p-4 flex flex-col justify-between min-h-[140px]">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<div
|
||||
class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<i class="{{ $uptimeIcon }} text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] tracking-wide uppercase text-white/70">Uptime</span>
|
||||
</div>
|
||||
|
|
@ -96,68 +100,90 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Dienste & Storage: kompakt & bündig --}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{{-- Dienste kompakt --}}
|
||||
<div class="glass-card p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<i class="ph ph-gear-six text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] tracking-wide uppercase text-white/70">Dienste</span>
|
||||
<div class="grid grid-cols-2 #items-center justify-between gap-3">
|
||||
{{-- MailGuard Status Card --}}
|
||||
<div
|
||||
class="glass-card p-4 flex flex-row items-start justify-between gap-4 relative overflow-hidden mb-4">
|
||||
{{-- Linke Seite: Icon + Titel --}}
|
||||
<div class="flex #items-start gap-3 relative z-10">
|
||||
<div class="shrink-0">
|
||||
{{-- Modernes Shield-Icon --}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||
<defs>
|
||||
<linearGradient id="shieldGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#4ade80"/>
|
||||
<stop offset="1" stop-color="#15803d"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="shine" cx="30%" cy="20%" r="70%">
|
||||
<stop offset="0%" stop-color="rgba(255,255,255,0.4)"/>
|
||||
<stop offset="100%" stop-color="rgba(255,255,255,0)"/>
|
||||
</radialGradient>
|
||||
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="0" stdDeviation="3" flood-color="#22c55e"
|
||||
flood-opacity="0.6"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<path d="M32 6l20 8v12c0 13.5-8.7 22.7-20 27-11.3-4.3-20-13.5-20-27V14l20-8z"
|
||||
fill="url(#shieldGradient)" filter="url(#glow)"/>
|
||||
<path d="M32 6l20 8v12c0 13.5-8.7 22.7-20 27-11.3-4.3-20-13.5-20-27V14l20-8z"
|
||||
fill="url(#shine)"/>
|
||||
<path d="M23 33l7 7 11-14" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-white/90">WoltGuard</h3>
|
||||
<p class="text-sm text-white/50">System-Wächter aktiv und fehlerfrei</p>
|
||||
</div>
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-white/5 border border-white/10 px-2 py-0.5 text-[10px] text-white/60">
|
||||
systemctl / TCP
|
||||
</span>
|
||||
</div>
|
||||
<ul class="overflow-auto divide-y divide-white/5">
|
||||
@forelse($servicesCompact as $s)
|
||||
<li class="grid grid-cols-[auto,1fr,auto] items-center gap-3 py-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="h-2 w-2 rounded-full {{ $s['dotClass'] }}"></span>
|
||||
<div class="min-w-0">
|
||||
<div class="text-white/90 truncate">{{ $s['label'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@if($s['hint'])
|
||||
<div class="text-[11px] text-white/45 truncate">{{ $s['hint'] }}</div>
|
||||
@endif
|
||||
</div>
|
||||
<span
|
||||
class="justify-self-end inline-flex items-center px-2.5 py-0.5 rounded-full text-xs border {{ $s['pillClass'] }}">
|
||||
{{ $s['pillText'] }}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
@empty
|
||||
<li class="py-2 text-white/50 text-sm">Keine Daten.</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
|
||||
{{-- Rechte Seite: Status & Avatar --}}
|
||||
<div class="flex items-start gap-3 relative z-10">
|
||||
{{-- Status Badge --}}
|
||||
@if($guardOk ?? false)
|
||||
<span
|
||||
class="inline-flex #items-center gap-1 px-3 py-1 rounded-full text-sm border border-emerald-400/30 text-emerald-300 bg-emerald-500/10">
|
||||
<i class="ph ph-check-circle text-[14px]"></i>
|
||||
<span class="text-[11px]">alle Dienste OK</span>
|
||||
</span>
|
||||
@else
|
||||
<span
|
||||
class="inline-flex #items-center gap-1 px-3 py-1 rounded-full text-sm border border-rose-400/30 text-rose-300 bg-rose-500/10">
|
||||
<i class="ph ph-warning-circle text-[11px]"></i>
|
||||
Störung erkannt
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<livewire:ui.system.update-card/>
|
||||
</div>
|
||||
|
||||
{{-- 2-Spalten Abschnitt: links Dienste, rechts Storage --}}
|
||||
<div class="glass-card relative p-4">
|
||||
{{-- Kopf: Titel + Link oben links --}}
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<i class="ph ph-hard-drives text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] tracking-wide uppercase text-white/70">Storage</span>
|
||||
</div>
|
||||
<a href="#"
|
||||
class="inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-[10px] text-white/70 hover:text-white hover:border-white/20 transition">
|
||||
Details <i class="ph ph-caret-right text-[12px]"></i>
|
||||
</a>
|
||||
</div>
|
||||
{{-- Dienste & Storage: kompakt & bündig --}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="glass-card relative p-4 max-h-fit">
|
||||
{{-- Inhalt: Donut links, Zahlen rechts – stacked auf kleineren Screens --}}
|
||||
<div class="grid grid-cols-1 #md:grid-cols-[minmax(220px,1fr)_minmax(220px,1fr)] #gap-6 items-center">
|
||||
<div class="grid grid-cols-1 items-center">
|
||||
{{-- Donut --}}
|
||||
<div class="flex items-center justify-between -mb-3">
|
||||
<div
|
||||
class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<i class="ph ph-hard-drives text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] tracking-wide uppercase text-white/70">Storage</span>
|
||||
</div>
|
||||
<a href="#"
|
||||
class="inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-[10px] text-white/70 hover:text-white hover:border-white/20 transition">
|
||||
Details <i class="ph ph-caret-right text-[12px]"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="relative"
|
||||
style="width: {{ $diskInnerSize + 80 }}px; height: {{ $diskInnerSize + 80 }}px;">
|
||||
{{-- Innerer grauer Kreis --}}
|
||||
<div class="absolute inset-[36px] rounded-full bg-white/[0.04] backdrop-blur-sm ring-1 ring-white/10"></div>
|
||||
<div
|
||||
class="absolute inset-[36px] rounded-full bg-white/[0.04] backdrop-blur-sm ring-1 ring-white/10"></div>
|
||||
|
||||
{{-- Prozentanzeige im Zentrum – leicht kleiner & feiner --}}
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
||||
|
|
@ -206,5 +232,47 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="glass-card p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div
|
||||
class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<i class="ph ph-gear-six text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] tracking-wide uppercase text-white/70">Dienste</span>
|
||||
</div>
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-full bg-white/5 border border-white/10 px-2 py-0.5 text-[10px] text-white/60">
|
||||
systemctl / TCP
|
||||
</span>
|
||||
</div>
|
||||
<ul class="overflow-auto divide-y divide-white/5">
|
||||
@forelse($servicesCompact as $s)
|
||||
<li class="grid grid-cols-[auto,1fr,auto] items-center gap-3 py-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="h-2 w-2 rounded-full {{ $s['dotClass'] }}"></span>
|
||||
<div class="min-w-0">
|
||||
<div class="text-white/90 truncate">{{ $s['label'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@if($s['hint'])
|
||||
<div class="text-[11px] text-white/45 truncate">{{ $s['hint'] }}</div>
|
||||
@endif
|
||||
</div>
|
||||
<span
|
||||
class="justify-self-end inline-flex items-center px-2.5 py-0.5 rounded-full text-xs border {{ $s['pillClass'] }}">
|
||||
{{ $s['pillText'] }}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
@empty
|
||||
<li class="py-2 text-white/50 text-sm">Keine Daten.</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
@php
|
||||
$hasUpdate = $latest && $current && version_compare($latest, $current, '>');
|
||||
@endphp
|
||||
|
||||
<div class="glass-card rounded-2xl p-4 border border-white/10 bg-white/5 max-h-fit">
|
||||
<div class="flex items-start gap-2">
|
||||
{{-- Shield-Bot --}}
|
||||
<div class="relative shrink-0">
|
||||
<div class="shrink-0 relative">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||
<defs>
|
||||
<linearGradient id="shieldGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#4ade80"></stop>
|
||||
<stop offset="1" stop-color="#15803d"></stop>
|
||||
</linearGradient>
|
||||
<radialGradient id="shine" cx="30%" cy="20%" r="70%">
|
||||
<stop offset="0%" stop-color="rgba(255,255,255,0.4)"></stop>
|
||||
<stop offset="100%" stop-color="rgba(255,255,255,0)"></stop>
|
||||
</radialGradient>
|
||||
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="0" stdDeviation="3" flood-color="#22c55e" flood-opacity="0.6"></feDropShadow>
|
||||
</filter>
|
||||
</defs>
|
||||
<path d="M32 6l20 8v12c0 13.5-8.7 22.7-20 27-11.3-4.3-20-13.5-20-27V14l20-8z" fill="url(#shieldGradient)" filter="url(#glow)"></path>
|
||||
<path d="M32 6l20 8v12c0 13.5-8.7 22.7-20 27-11.3-4.3-20-13.5-20-27V14l20-8z" fill="url(#shine)"></path>
|
||||
<i class="ph-bold ph-arrows-clockwise absolute top-1/2 left-1/2 -translate-1/2 text-2xl {{ $state === 'running' ? 'animate-spin' : '' }}"></i>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="text-white/90 font-semibold">MailWolt Update</div>
|
||||
{{-- kleine Statuszeile mit Versionen, wenn vorhanden --}}
|
||||
<div class="text-xs text-white/70">
|
||||
@if($current)
|
||||
aktuell: <span class="text-white/90">v{{ $current }}</span>
|
||||
@else
|
||||
aktuell: <span class="text-white/60">–</span>
|
||||
@endif
|
||||
@if($latest)
|
||||
<span class="mx-1 text-white/30">•</span>
|
||||
verfügbar: <span class="text-emerald-200">v{{ $latest }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{-- Badge rechts --}}
|
||||
<span class="shrink-0 inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[11px] border px-3 py-1
|
||||
{{ $hasUpdate
|
||||
? 'text-yellow-200 bg-yellow-500/10 border-yellow-400/30'
|
||||
: 'text-emerald-200 bg-emerald-500/10 border-emerald-400/30' }}">
|
||||
<i class="ph {{ $hasUpdate ? 'ph-arrow-fat-line-up' : 'ph-check-circle' }} text-[12px]"></i>
|
||||
{{ $hasUpdate ? 'Update verfügbar' : 'Aktuell' }}
|
||||
</span>
|
||||
{{-- <button wire:click="refreshState"--}}
|
||||
{{-- @disabled($state==='running')--}}
|
||||
{{-- class="inline-flex items-center gap-2 rounded p-1.5--}}
|
||||
{{-- text-white/75 bg-white/5 border border-white/10 hover:border-white/20--}}
|
||||
{{-- disabled:opacity-60">--}}
|
||||
{{-- <i class="ph ph-arrow-clockwise text-[11px]"></i>--}}
|
||||
{{-- </button>--}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center gap-2">
|
||||
@if($hasUpdate)
|
||||
<button wire:click="runUpdate"
|
||||
@disabled($state==='running')
|
||||
class="inline-flex items-center gap-2 rounded-lg px-3 py-1.5
|
||||
text-emerald-200 bg-emerald-500/10 border border-emerald-400/30
|
||||
hover:bg-emerald-500/15 hover:border-emerald-300/50
|
||||
disabled:opacity-60">
|
||||
<i class="ph ph-arrow-fat-lines-up text-[14px]"></i> Jetzt aktualisieren
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Progress-Bar, wenn running --}}
|
||||
@if($state === 'running')
|
||||
<div class="mt-3 h-1.5 rounded bg-white/10 overflow-hidden">
|
||||
<div class="h-full bg-emerald-400/70 animate-[progress_1.4s_infinite]" style="width:40%"></div>
|
||||
</div>
|
||||
<style>@keyframes progress {
|
||||
0% {
|
||||
transform: translateX(-100%)
|
||||
}
|
||||
100% {
|
||||
transform: translateX(260%)
|
||||
}
|
||||
}</style>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -5,10 +5,10 @@
|
|||
@section('header_title', 'Dashboard')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-7xl mx-auto space-y-6 px-2 md:px-4">
|
||||
<div class="max-w-fit col-span-4 #lg:col-span-6">
|
||||
<livewire:ui.system.update-manager />
|
||||
</div>
|
||||
<div class="max-w-9xl mx-auto space-y-6 px-2 md:px-4">
|
||||
{{-- <div class="max-w-fit col-span-4 #lg:col-span-6">--}}
|
||||
{{-- <livewire:ui.system.update-manager />--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
<div class="col-span-12 lg:col-span-6">
|
||||
<livewire:ui.dashboard.top-bar />
|
||||
|
|
@ -18,20 +18,9 @@
|
|||
<livewire:ui.dashboard.health-card />
|
||||
</div>
|
||||
|
||||
{{-- <div class="grid grid-cols-1 lg:grid-cols-3 gap-4">--}}
|
||||
{{-- @livewire('ui.dashboard.services-health') --}}{{----}}{{-- NEU --}}
|
||||
{{-- @livewire('ui.dashboard.mail-kpis-card', ['key'=>'outgoing_queue'])--}}
|
||||
{{-- @livewire('ui.dashboard.app-updates') --}}{{-- NEU --}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">--}}
|
||||
{{-- @livewire('ui.dashboard.mail-trend-chart')--}}
|
||||
{{-- @livewire('ui.dashboard.alerts-feed') --}}{{-- NEU --}}
|
||||
{{-- </div>--}}
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
@livewire('ui.dashboard.domains-panel')
|
||||
@livewire('ui.dashboard.recent-logins-table')
|
||||
<livewire:ui.dashboard.domains-panel />
|
||||
<livewire:ui.dashboard.recent-logins-table />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,57 +2,55 @@
|
|||
|
||||
use App\Http\Controllers\Api\TaskFeedController;
|
||||
use App\Http\Controllers\Auth\LoginController;
|
||||
use App\Http\Controllers\Setup\SetupWizard;
|
||||
use App\Http\Controllers\Auth\SignUpController;
|
||||
use App\Http\Controllers\UI\Domain\DomainDnsController;
|
||||
use App\Http\Controllers\UI\Mail\AliasController;
|
||||
use App\Http\Controllers\UI\Mail\MailboxController;
|
||||
use App\Http\Controllers\UI\Security\RecoveryCodeDownloadController;
|
||||
use App\Http\Controllers\UI\Security\SecurityController;
|
||||
use App\Http\Controllers\UI\System\SettingsController;
|
||||
use App\Livewire\PingButton;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/', function () {
|
||||
return view('welcome');
|
||||
return Auth::check()
|
||||
? redirect()->route('ui.dashboard')
|
||||
: redirect()->route('login');
|
||||
});
|
||||
|
||||
Route::middleware('auth.user')->name('ui.')->group(function () {
|
||||
#DASHBOARD ROUTE
|
||||
Route::get('/dashboard', [\App\Http\Controllers\UI\DashboardController::class, 'index'])->name('dashboard');
|
||||
|
||||
Route::get('/dashboard', [\App\Http\Controllers\UI\DashboardController::class, 'index'])
|
||||
->middleware(['auth']) // falls gewünscht
|
||||
->name('ui.dashboard');
|
||||
|
||||
//Route::middleware(['auth']) // falls du auth nutzt
|
||||
//->get('/system/settings', [SettingsController::class, 'index'])
|
||||
// ->name('ui.system.settings');
|
||||
|
||||
Route::middleware(['auth'])
|
||||
->prefix('system')
|
||||
->name('ui.system.')
|
||||
->group(function () {
|
||||
Route::get('/settings', [\App\Http\Controllers\UI\System\SettingsController::class, 'index'])
|
||||
->name('settings');
|
||||
#SYSTEM ROUTES
|
||||
Route::prefix('system')->name('system.')->group(function () {
|
||||
Route::get('/settings', [\App\Http\Controllers\UI\System\SettingsController::class, 'index'])->name('settings');
|
||||
});
|
||||
|
||||
Route::middleware(['auth'])
|
||||
->prefix('security')
|
||||
->name('ui.security.')
|
||||
->group(function () {
|
||||
#SECURITY ROUTES
|
||||
Route::prefix('security')->name('security.')->group(function () {
|
||||
Route::get('/', [SecurityController::class, 'index'])->name('index');
|
||||
Route::get('/ssl', [SecurityController::class, 'ssl'])->name('ssl');
|
||||
Route::get('/fail2ban', [SecurityController::class, 'fail2ban'])->name('fail2ban');
|
||||
Route::get('/rspamd', [SecurityController::class, 'rspamd'])->name('rspamd');
|
||||
Route::get('/tls-ciphers', [SecurityController::class, 'tlsCiphers'])->name('tls');
|
||||
Route::get('/audit-logs', [SecurityController::class, 'auditLogs'])->name('audit');
|
||||
});
|
||||
|
||||
#DOMAIN ROUTES
|
||||
Route::name('domain.')->group(function () {
|
||||
Route::get('/domains', [DomainDnsController::class, 'index'])->name('index');
|
||||
});
|
||||
|
||||
#MAIL ROUTES
|
||||
Route::name('mail.')->group(function () {
|
||||
Route::get('/mailboxes', [MailboxController::class, 'index'])->name('mailbox.index');
|
||||
Route::get('/aliases', [AliasController::class, 'index'])->name('aliases.index');
|
||||
});
|
||||
|
||||
#LOGOUT ROUTE
|
||||
Route::post('/logout', [LoginController::class, 'logout'])->name('logout');
|
||||
});
|
||||
|
||||
Route::middleware(['auth'])->name('ui.domain.')->group(function () {
|
||||
Route::get('/domains', [DomainDnsController::class, 'index'])->name('index');
|
||||
});
|
||||
|
||||
Route::middleware(['auth'])->name('ui.mail.')->group(function () {
|
||||
Route::get('/mailboxes', [MailboxController::class, 'index'])->name('mailbox.index');
|
||||
Route::get('/aliases', [AliasController::class, 'index'])->name('aliases.index');
|
||||
});
|
||||
|
||||
|
||||
//Route::middleware(['auth'])
|
||||
|
|
@ -61,32 +59,12 @@ Route::middleware(['auth'])->name('ui.mail.')->group(function () {
|
|||
// ->middleware('signed'); // wichtig: signierte URL
|
||||
|
||||
|
||||
Route::middleware(['web','auth']) // nutzt Session, kein Token nötig
|
||||
Route::middleware(['web', 'auth']) // nutzt Session, kein Token nötig
|
||||
->get('/ui/tasks/active', [TaskFeedController::class, 'active'])
|
||||
->name('ui.tasks.active');
|
||||
|
||||
//Route::middleware(['auth'])->group(function () {
|
||||
// Route::get('/tasks/active', [TaskFeedController::class, 'active'])
|
||||
// ->name('tasks.active');
|
||||
// Route::post('/tasks/ack', [TaskFeedController::class, 'ack'])
|
||||
// ->name('tasks.ack');
|
||||
//});
|
||||
//Route::middleware(['web','auth']) // gleiche Session wie im Dashboard
|
||||
//->get('/ui/tasks/active', [\App\Http\Controllers\Api\TaskStatusController::class, 'index'])
|
||||
// ->name('ui.tasks.active');
|
||||
Route::middleware('guest.only')->group(function () {
|
||||
Route::get('/login', [LoginController::class, 'show'])->name('login');
|
||||
Route::get('/signup', [SignUpController::class, 'show'])->middleware('signup.open')->name('signup');
|
||||
});
|
||||
|
||||
//Route::get('/dashboard', [DashboardController::class, 'show'])->name('dashboard');
|
||||
Route::get('/login', [LoginController::class, 'show'])->name('login');
|
||||
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::middleware(['auth', 'ensure.setup'])->group(function () {
|
||||
//// Route::get('/dashboard', Dashboard::class)->name('dashboard');
|
||||
// Route::get('/setup', [SetupWizard::class, 'show'])->name('setup.wizard');
|
||||
//});
|
||||
|
||||
|
||||
|
||||
//Route::middleware(['auth', 'ensure.setup'])->group(function () {
|
||||
// Route::get('/dashboard', fn() => view('dashboard'))->name('dashboard');
|
||||
//});
|
||||
|
|
|
|||
Loading…
Reference in New Issue