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:
sysops
2026-03-31 10:44:02 +02:00
parent 5bbf6d0ff3
commit 58bcfb1586
6 changed files with 224 additions and 6 deletions
+29 -4
View File
@@ -154,6 +154,17 @@ func (s *Server) handleSystemStats(w http.ResponseWriter, r *http.Request) {
disks = []diskStat{}
}
// Uptime: /proc/uptime
uptimeResp := map[string]interface{}{"seconds": 0.0}
if data, err := os.ReadFile("/proc/uptime"); err == nil {
parts := strings.Fields(string(data))
if len(parts) >= 1 {
if secs, err := strconv.ParseFloat(parts[0], 64); err == nil {
uptimeResp["seconds"] = secs
}
}
}
// Archive: first & last mail
archiveResp := map[string]interface{}{"first_mail": nil, "last_mail": nil}
first, last, err := s.store.FirstAndLastMail()
@@ -166,11 +177,25 @@ func (s *Server) handleSystemStats(w http.ResponseWriter, r *http.Request) {
}
}
// Mail activity (60min / 24h / 7d / 30d)
activity, _ := s.store.MailActivityStats(r.Context())
// Storage estimate — use free bytes of the first non-root disk (or root)
var freeDiskBytes uint64
for _, d := range disks {
freeDiskBytes = d.FreeBytes
break
}
estimate, _ := s.store.StorageEstimateStats(r.Context(), freeDiskBytes)
writeJSON(w, http.StatusOK, map[string]interface{}{
"cpu": cpuResp,
"ram": ramResp,
"disks": disks,
"archive": archiveResp,
"cpu": cpuResp,
"ram": ramResp,
"disks": disks,
"uptime": uptimeResp,
"archive": archiveResp,
"activity": activity,
"estimate": estimate,
})
}