SQLite durch PostgreSQL + TimescaleDB ersetzt

- Backend DB komplett auf PostgreSQL 17 + TimescaleDB 2.25.1 umgestellt
- pgx pure-Go Driver (kein CGO), modernc/sqlite entfernt
- Hypertable 'metrics' fuer Time-Series Daten
- Automatische Metrik-Extraktion bei jedem Heartbeat:
  CPU, RAM, Uptime, Disk, Network-Traffic, Gateway RTT/Loss
- Retention Policy: 90 Tage, Compression nach 7 Tagen
- Neue API-Endpoints: GET /metrics (raw) + GET /metrics/summary (aggregiert)
- time_bucket() Aggregation (5min, 1h, etc.)
- config.yaml: database-Block statt db_path
- docs/POSTGRES_SETUP.md: Installationsanleitung
- README aktualisiert
This commit is contained in:
cynfo3000
2026-02-28 12:48:14 +01:00
parent 27d6492258
commit 5d7a3cbcc1
9 changed files with 822 additions and 429 deletions
+83
View File
@@ -0,0 +1,83 @@
package api
import (
"net/http"
"strconv"
"time"
)
// GET /api/v1/agents/{id}/metrics — Metriken abfragen
func (h *Handler) getMetrics() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
agentID := r.PathValue("id")
metric := r.URL.Query().Get("metric")
// Zeitraum (default: letzte 24h)
from := time.Now().Add(-24 * time.Hour)
to := time.Now()
if f := r.URL.Query().Get("from"); f != "" {
if t, err := time.Parse(time.RFC3339, f); err == nil {
from = t
}
}
if t := r.URL.Query().Get("to"); t != "" {
if parsed, err := time.Parse(time.RFC3339, t); err == nil {
to = parsed
}
}
limit := 1000
if l := r.URL.Query().Get("limit"); l != "" {
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
limit = parsed
}
}
results, err := h.db.GetMetrics(agentID, metric, from, to, limit)
if err != nil {
writeError(w, http.StatusInternalServerError, "Metriken laden fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, results)
}
}
// GET /api/v1/agents/{id}/metrics/summary — Aggregierte Metriken (Durchschnitt/Min/Max)
func (h *Handler) getMetricsSummary() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
agentID := r.PathValue("id")
metric := r.URL.Query().Get("metric")
if metric == "" {
writeError(w, http.StatusBadRequest, "Parameter 'metric' erforderlich")
return
}
bucket := r.URL.Query().Get("bucket")
if bucket == "" {
bucket = "5 minutes"
}
from := time.Now().Add(-24 * time.Hour)
to := time.Now()
if f := r.URL.Query().Get("from"); f != "" {
if t, err := time.Parse(time.RFC3339, f); err == nil {
from = t
}
}
if t := r.URL.Query().Get("to"); t != "" {
if parsed, err := time.Parse(time.RFC3339, t); err == nil {
to = parsed
}
}
results, err := h.db.GetMetricsSummary(agentID, metric, bucket, from, to)
if err != nil {
writeError(w, http.StatusInternalServerError, "Metriken laden fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, results)
}
}
+4
View File
@@ -26,6 +26,10 @@ func (h *Handler) setupTunnelRoutes(mux *http.ServeMux, hub *ws.Hub) {
mux.HandleFunc("PUT /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
mux.HandleFunc("DELETE /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
// Metrics-Endpoints
mux.HandleFunc("GET /api/v1/agents/{id}/metrics", h.getMetrics())
mux.HandleFunc("GET /api/v1/agents/{id}/metrics/summary", h.getMetricsSummary())
// WireGuard-Endpoints
mux.HandleFunc("POST /api/v1/agents/{id}/wireguard/peers", h.wgAddPeer(hub))
mux.HandleFunc("GET /api/v1/agents/{id}/wireguard/peers", h.wgListPeers(hub))