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:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user