feat: Dashboard-Metriken nach Mailpiler-Vorbild (Uptime, Aktivität, Prognose)
- storage_stats.go (neu): MailActivityStats (60min/24h/7d/30d), StorageEstimateStats - dashboard_handlers.go: Uptime (/proc/uptime), activity + estimate in System-Stats-Response - DashboardTab: Uptime in API-Kachel, neue Kacheln "Mail-Eingang" + "Speicherprognose" - Warnung (Badge "Knapp!") wenn Partition in <90 Tagen voll Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,8 @@ import {
|
||||
type SMTPStatus,
|
||||
type StorageStats,
|
||||
type SystemStats,
|
||||
type SystemStatsActivity,
|
||||
type SystemStatsEstimate,
|
||||
} from "@/lib/api";
|
||||
import { type User } from "@/lib/api";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -20,6 +22,73 @@ function formatBytes(bytes: number): string {
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||
}
|
||||
|
||||
function formatUptime(seconds: number): string {
|
||||
const d = Math.floor(seconds / 86400);
|
||||
const h = Math.floor((seconds % 86400) / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
if (d > 0) return `${d}d ${h}h ${m}m`;
|
||||
if (h > 0) return `${h}h ${m}m`;
|
||||
return `${m}m`;
|
||||
}
|
||||
|
||||
function formatDaysUntilFull(days: number): string {
|
||||
if (days < 0) return "∞";
|
||||
if (days < 30) return `${days} Tage`;
|
||||
if (days < 365) return `${Math.round(days / 30)} Monate`;
|
||||
return `${(days / 365).toFixed(1)} Jahre`;
|
||||
}
|
||||
|
||||
function ActivityCard({ activity }: { activity: SystemStatsActivity }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="pt-6 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-muted-foreground">Mail-Eingang</span>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="grid grid-cols-2 gap-1 text-sm">
|
||||
<span className="text-muted-foreground">Letzte 60 min</span>
|
||||
<span className="font-semibold">{activity.last_60_min.toLocaleString("de-DE")}</span>
|
||||
<span className="text-muted-foreground">Letzte 24 h</span>
|
||||
<span className="font-semibold">{activity.last_24h.toLocaleString("de-DE")}</span>
|
||||
<span className="text-muted-foreground">Letzte 7 Tage</span>
|
||||
<span className="font-semibold">{activity.last_7d.toLocaleString("de-DE")}</span>
|
||||
<span className="text-muted-foreground">Letzte 30 Tage</span>
|
||||
<span className="font-semibold">{activity.last_30d.toLocaleString("de-DE")}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function EstimateCard({ estimate }: { estimate: SystemStatsEstimate }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="pt-6 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-muted-foreground">Speicherprognose</span>
|
||||
{estimate.days_until_full >= 0 && estimate.days_until_full < 90 && (
|
||||
<Badge variant="destructive">Knapp!</Badge>
|
||||
)}
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="grid grid-cols-2 gap-1 text-sm">
|
||||
<span className="text-muted-foreground">Ø Mails/Tag</span>
|
||||
<span className="font-semibold">{estimate.avg_mails_per_day.toFixed(1)}</span>
|
||||
<span className="text-muted-foreground">Ø Mail-Größe</span>
|
||||
<span>{formatBytes(estimate.avg_mail_bytes)}</span>
|
||||
<span className="text-muted-foreground">Archiv-Alter</span>
|
||||
<span>{estimate.archive_age_days} Tage</span>
|
||||
<span className="text-muted-foreground">Partition voll in</span>
|
||||
<span className={estimate.days_until_full >= 0 && estimate.days_until_full < 90 ? "font-semibold text-destructive" : "font-semibold"}>
|
||||
{formatDaysUntilFull(estimate.days_until_full)}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
interface DashboardTabProps {
|
||||
isSuperAdmin: boolean;
|
||||
smtpStatus: SMTPStatus | null;
|
||||
@@ -74,7 +143,7 @@ export function DashboardTab({
|
||||
{/* Status-Kacheln */}
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
|
||||
{/* API */}
|
||||
{/* API + Uptime */}
|
||||
<Card>
|
||||
<CardContent className="pt-6 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -89,6 +158,12 @@ export function DashboardTab({
|
||||
<span className="font-mono">:8080</span>
|
||||
<span className="text-muted-foreground">Protokoll</span>
|
||||
<span>HTTP</span>
|
||||
{systemStats?.uptime && (
|
||||
<>
|
||||
<span className="text-muted-foreground">System-Uptime</span>
|
||||
<span>{formatUptime(systemStats.uptime.seconds)}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -293,6 +368,17 @@ export function DashboardTab({
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Mail-Aktivität + Speicherprognose */}
|
||||
{(systemStats.activity || systemStats.estimate) && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">Archiv-Statistik</h3>
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{systemStats.activity && <ActivityCard activity={systemStats.activity} />}
|
||||
{systemStats.estimate && <EstimateCard estimate={systemStats.estimate} />}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Festplatten */}
|
||||
{systemStats.disks.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -123,6 +123,8 @@ export type {
|
||||
SystemStatsRAM,
|
||||
SystemStatsDisk,
|
||||
SystemStatsMailInfo,
|
||||
SystemStatsActivity,
|
||||
SystemStatsEstimate,
|
||||
SystemStats,
|
||||
SecurityCheck,
|
||||
SecurityAuditResult,
|
||||
|
||||
@@ -83,14 +83,31 @@ export interface SystemStatsMailInfo {
|
||||
subject: string;
|
||||
}
|
||||
|
||||
export interface SystemStatsActivity {
|
||||
last_60_min: number;
|
||||
last_24h: number;
|
||||
last_7d: number;
|
||||
last_30d: number;
|
||||
}
|
||||
|
||||
export interface SystemStatsEstimate {
|
||||
avg_mails_per_day: number;
|
||||
avg_mail_bytes: number;
|
||||
days_until_full: number; // -1 = unknown
|
||||
archive_age_days: number;
|
||||
}
|
||||
|
||||
export interface SystemStats {
|
||||
cpu: SystemStatsCPU;
|
||||
ram: SystemStatsRAM;
|
||||
disks: SystemStatsDisk[];
|
||||
uptime: { seconds: number };
|
||||
archive: {
|
||||
first_mail: SystemStatsMailInfo | null;
|
||||
last_mail: SystemStatsMailInfo | null;
|
||||
};
|
||||
activity: SystemStatsActivity;
|
||||
estimate: SystemStatsEstimate;
|
||||
}
|
||||
|
||||
export interface SecurityCheck {
|
||||
|
||||
Reference in New Issue
Block a user