feat(PROJ-40,PROJ-41): Prometheus Metriken + Dashboard Zeitreihe
- PROJ-40: /api/health mit Version+Uptime, /metrics Prometheus-Format (mails_last_60min/24h/7d/30d, mails_total, storage_bytes, tenants_total, users_total, uptime_seconds) — Token-Schutz optional konfigurierbar - PROJ-41: GET /api/admin/stats/timeseries (30-Tage tagesgenau, Tenant-scoped) + SVG-Balkendiagramm im Dashboard (Mail-Eingang letzte 30 Tage) - storage.DBQueryRow() Helper für Metrics-Queries ohne Pool-Exposition - config.MetricsConfig (enabled, token) in config.go Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+19
-11
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"regexp"
|
||||
|
||||
@@ -61,6 +62,8 @@ const (
|
||||
// Server is the archivmail HTTP API server.
|
||||
type Server struct {
|
||||
cfg config.APIConfig
|
||||
metricsCfg config.MetricsConfig
|
||||
startTime time.Time
|
||||
store *storage.Store
|
||||
idx index.Indexer
|
||||
authMgr *auth.Manager
|
||||
@@ -122,6 +125,11 @@ func (s *Server) SetGlobalRetentionDays(days int) {
|
||||
s.globalRetentionDays = days
|
||||
}
|
||||
|
||||
// SetMetrics wires the metrics config into the API server.
|
||||
func (s *Server) SetMetrics(cfg config.MetricsConfig) {
|
||||
s.metricsCfg = cfg
|
||||
}
|
||||
|
||||
// SetMailer wires the SMTP-Out mailer into the API server (PROJ-28).
|
||||
func (s *Server) SetMailer(m *mailer.Mailer) {
|
||||
s.mailer = m
|
||||
@@ -153,14 +161,15 @@ func New(
|
||||
logger *slog.Logger,
|
||||
) *Server {
|
||||
s := &Server{
|
||||
cfg: cfg,
|
||||
store: store,
|
||||
idx: idx,
|
||||
authMgr: authMgr,
|
||||
users: users,
|
||||
audlog: audlog,
|
||||
logger: logger,
|
||||
mux: http.NewServeMux(),
|
||||
cfg: cfg,
|
||||
store: store,
|
||||
idx: idx,
|
||||
authMgr: authMgr,
|
||||
users: users,
|
||||
audlog: audlog,
|
||||
logger: logger,
|
||||
mux: http.NewServeMux(),
|
||||
startTime: time.Now(),
|
||||
}
|
||||
s.routes()
|
||||
return s
|
||||
@@ -178,6 +187,7 @@ func (s *Server) authAdmin(h http.HandlerFunc) http.HandlerFunc {
|
||||
|
||||
func (s *Server) routes() {
|
||||
s.mux.HandleFunc("GET /api/health", s.handleHealth)
|
||||
s.mux.HandleFunc("GET /metrics", s.handleMetrics)
|
||||
s.mux.HandleFunc("GET /api/version", s.handleVersion)
|
||||
s.mux.HandleFunc("POST /api/auth/login", s.handleLogin)
|
||||
s.mux.HandleFunc("GET /api/auth/me", s.auth(s.handleMe))
|
||||
@@ -206,6 +216,7 @@ func (s *Server) routes() {
|
||||
s.mux.HandleFunc("POST /api/admin/services/{name}/action", s.authAdmin(s.handleServiceAction))
|
||||
|
||||
s.mux.HandleFunc("GET /api/admin/system/stats", s.authAdmin(s.handleSystemStats))
|
||||
s.mux.HandleFunc("GET /api/admin/stats/timeseries", s.authAdmin(s.handleMailTimeseries))
|
||||
s.mux.HandleFunc("GET /api/admin/security/audit", s.authAdmin(s.handleSecurityAudit))
|
||||
// SEC-17: Security fix actions require superadmin, not just domain_admin.
|
||||
s.mux.HandleFunc("POST /api/admin/security/fix", s.auth(s.requireRole(userstore.RoleSuperAdmin, s.handleSecurityFix)))
|
||||
@@ -288,9 +299,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// --- handlers ---
|
||||
|
||||
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// --- middleware ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user