feat: rollenbasierte SMTP-Statistik + Service-Aktionen
- handleServiceAction: nur superadmin darf Dienste stoppen/starten - handleSMTPStatus: domain_admin bekommt tenant-gefilterte Stats (Domains, Mailanzahl, Speicher) statt globaler Daemon-Info - Admin-Dashboard: SMTP-Kacheln, Systemauslastung, IP-Allowlist nur für superadmin; domain_admin sieht eigene Domain-Statistik - Dienste-Tab: Aktions-Buttons nur für superadmin sichtbar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+86
-58
@@ -673,59 +673,85 @@ export default function AdminPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* SMTP */}
|
||||
<Card>
|
||||
<CardContent className="pt-6 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-muted-foreground">SMTP-Daemon</span>
|
||||
<Badge variant={smtpStatus?.running ? "default" : "destructive"}>
|
||||
{smtpStatus?.running ? "Aktiv" : smtpStatus?.enabled === false ? "Deaktiviert" : "Gestoppt"}
|
||||
</Badge>
|
||||
</div>
|
||||
<Separator />
|
||||
{smtpStatus ? (
|
||||
<div className="grid grid-cols-2 gap-1 text-sm">
|
||||
<span className="text-muted-foreground">Adresse</span>
|
||||
<span className="font-mono">{smtpStatus.bind}</span>
|
||||
<span className="text-muted-foreground">Domain</span>
|
||||
<span className="font-mono">{smtpStatus.domain || "–"}</span>
|
||||
<span className="text-muted-foreground">TLS</span>
|
||||
<span>{smtpStatus.tls ? "Ja" : "Nein"}</span>
|
||||
<span className="text-muted-foreground">Max. Größe</span>
|
||||
<span>{smtpStatus.max_size_mb > 0 ? `${smtpStatus.max_size_mb} MB` : "50 MB"}</span>
|
||||
{/* SMTP — superadmin: globaler Daemon; domain_admin: nur eigene Domain-Statistik */}
|
||||
{isSuperAdmin ? (
|
||||
<>
|
||||
<Card>
|
||||
<CardContent className="pt-6 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-muted-foreground">SMTP-Daemon</span>
|
||||
<Badge variant={smtpStatus?.running ? "default" : "destructive"}>
|
||||
{smtpStatus?.running ? "Aktiv" : smtpStatus?.enabled === false ? "Deaktiviert" : "Gestoppt"}
|
||||
</Badge>
|
||||
</div>
|
||||
<Separator />
|
||||
{smtpStatus ? (
|
||||
<div className="grid grid-cols-2 gap-1 text-sm">
|
||||
<span className="text-muted-foreground">Adresse</span>
|
||||
<span className="font-mono">{smtpStatus.bind}</span>
|
||||
<span className="text-muted-foreground">Domain</span>
|
||||
<span className="font-mono">{smtpStatus.domain || "–"}</span>
|
||||
<span className="text-muted-foreground">TLS</span>
|
||||
<span>{smtpStatus.tls ? "Ja" : "Nein"}</span>
|
||||
<span className="text-muted-foreground">Max. Größe</span>
|
||||
<span>{smtpStatus.max_size_mb > 0 ? `${smtpStatus.max_size_mb} MB` : "50 MB"}</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">Nicht erreichbar</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-muted-foreground">SMTP Statistik</span>
|
||||
<span className="text-xs text-muted-foreground">seit letztem Start</span>
|
||||
</div>
|
||||
<Separator />
|
||||
{smtpStatus ? (
|
||||
<div className="grid grid-cols-2 gap-1 text-sm">
|
||||
<span className="text-muted-foreground">Empfangen</span>
|
||||
<span className="font-semibold text-green-600">{smtpStatus.received}</span>
|
||||
<span className="text-muted-foreground">Abgelehnt</span>
|
||||
<span className="font-semibold text-red-500">{smtpStatus.rejected}</span>
|
||||
<span className="text-muted-foreground">Letzte Mail</span>
|
||||
<span className="text-xs">
|
||||
{smtpStatus.last_mail_at
|
||||
? new Date(smtpStatus.last_mail_at).toLocaleString("de-DE")
|
||||
: "–"}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">Keine Daten</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
<Card>
|
||||
<CardContent className="pt-6 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-muted-foreground">SMTP – meine Domain(s)</span>
|
||||
<Badge variant="secondary">Tenant</Badge>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">Nicht erreichbar</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* SMTP Statistik (nur live via SMTP-Daemon) */}
|
||||
<Card>
|
||||
<CardContent className="pt-6 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-muted-foreground">SMTP Statistik</span>
|
||||
<span className="text-xs text-muted-foreground">seit letztem Start</span>
|
||||
</div>
|
||||
<Separator />
|
||||
{smtpStatus ? (
|
||||
<div className="grid grid-cols-2 gap-1 text-sm">
|
||||
<span className="text-muted-foreground">Empfangen</span>
|
||||
<span className="font-semibold text-green-600">{smtpStatus.received}</span>
|
||||
<span className="text-muted-foreground">Abgelehnt</span>
|
||||
<span className="font-semibold text-red-500">{smtpStatus.rejected}</span>
|
||||
<span className="text-muted-foreground">Letzte Mail</span>
|
||||
<span className="text-xs">
|
||||
{smtpStatus.last_mail_at
|
||||
? new Date(smtpStatus.last_mail_at).toLocaleString("de-DE")
|
||||
: "–"}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">Keine Daten</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Separator />
|
||||
{smtpStatus ? (
|
||||
<div className="grid grid-cols-2 gap-1 text-sm">
|
||||
<span className="text-muted-foreground">Domain(s)</span>
|
||||
<span className="font-mono text-xs">
|
||||
{smtpStatus.domains?.length > 0 ? smtpStatus.domains.join(", ") : "–"}
|
||||
</span>
|
||||
<span className="text-muted-foreground">Archivierte Mails</span>
|
||||
<span className="font-semibold">{smtpStatus.total_mails?.toLocaleString("de-DE") ?? "–"}</span>
|
||||
<span className="text-muted-foreground">Speicher</span>
|
||||
<span>{smtpStatus.total_bytes ? formatBytes(smtpStatus.total_bytes) : "–"}</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">Keine Daten</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Archiv-Speicher */}
|
||||
<Card>
|
||||
@@ -753,8 +779,8 @@ export default function AdminPage() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* System Stats: CPU, RAM, Disks, Archivzeitraum */}
|
||||
<div className="space-y-4">
|
||||
{/* System Stats: nur für superadmin */}
|
||||
{isSuperAdmin && <div className="space-y-4">
|
||||
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">Systemauslastung</h3>
|
||||
{!systemStats ? (
|
||||
<Alert variant="destructive">
|
||||
@@ -885,10 +911,10 @@ export default function AdminPage() {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
{/* IP-Allowlist */}
|
||||
{smtpStatus && smtpStatus.allowed_ips?.length > 0 && (
|
||||
{/* IP-Allowlist — nur superadmin */}
|
||||
{isSuperAdmin && smtpStatus && smtpStatus.allowed_ips?.length > 0 && (
|
||||
<Card>
|
||||
<CardContent className="pt-6 space-y-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">SMTP IP-Allowlist</span>
|
||||
@@ -978,7 +1004,7 @@ export default function AdminPage() {
|
||||
<TableHead className="w-24">Autostart</TableHead>
|
||||
<TableHead className="w-28">Externer Zugriff</TableHead>
|
||||
<TableHead>Beschreibung</TableHead>
|
||||
<TableHead className="w-72 text-right">Aktionen</TableHead>
|
||||
{isSuperAdmin && <TableHead className="w-72 text-right">Aktionen</TableHead>}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -1029,6 +1055,7 @@ export default function AdminPage() {
|
||||
<TableCell className="text-sm text-muted-foreground truncate max-w-xs">
|
||||
{svc.description || "–"}
|
||||
</TableCell>
|
||||
{isSuperAdmin && (
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-1 flex-wrap">
|
||||
{isActive ? (
|
||||
@@ -1102,6 +1129,7 @@ export default function AdminPage() {
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user