Files
NexaVPN/backend/internal/gateway/handler.go
nessi 610c5459e5 feat: add device traffic metrics with gateway telemetry reporting and admin UI display
Add rx_bytes and tx_bytes fields to Device type and API responses. Add formatDataSize helper for human-readable byte formatting with units from B to TB. Add Received and Sent columns to devices table in admin UI with formatted traffic totals. Add traffic metrics display to device action panel.

Add TelemetrySnapshot and PeerTelemetry types for gateway runtime stats. Add gateway telemetry endpoint at POST /gateway
2026-03-18 07:43:22 +01:00

112 lines
3.2 KiB
Go

package gateway
import (
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
"nexavpn/backend/internal/apiutil"
)
type Handler struct {
service *Service
bootstrapToken string
}
func NewHandler(service *Service, bootstrapToken string) *Handler {
return &Handler{service: service, bootstrapToken: bootstrapToken}
}
func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
items, err := h.service.List(r.Context())
if err != nil {
apiutil.Error(w, http.StatusInternalServerError, "gateways_list_failed", "unable to list gateways")
return
}
apiutil.JSON(w, http.StatusOK, items)
}
func (h *Handler) SyncBundle(w http.ResponseWriter, r *http.Request) {
bundle, err := h.service.BuildSyncBundle(r.Context(), chi.URLParam(r, "id"))
if err != nil {
apiutil.Error(w, http.StatusBadRequest, "gateway_sync_failed", "unable to build sync bundle")
return
}
apiutil.JSON(w, http.StatusOK, bundle)
}
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
var input UpdateRequest
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
apiutil.Error(w, http.StatusBadRequest, "invalid_json", "invalid request body")
return
}
item, err := h.service.Update(r.Context(), chi.URLParam(r, "id"), input)
if err != nil {
apiutil.Error(w, http.StatusBadRequest, "gateway_update_failed", "unable to update gateway")
return
}
apiutil.JSON(w, http.StatusOK, item)
}
func (h *Handler) Bootstrap(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Gateway-Bootstrap-Token") != h.bootstrapToken {
apiutil.Error(w, http.StatusUnauthorized, "unauthorized", "invalid gateway bootstrap token")
return
}
var input BootstrapRequest
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
apiutil.Error(w, http.StatusBadRequest, "invalid_json", "invalid request body")
return
}
item, err := h.service.Bootstrap(r.Context(), input)
if err != nil {
apiutil.Error(w, http.StatusBadRequest, "gateway_bootstrap_failed", "unable to bootstrap gateway")
return
}
apiutil.JSON(w, http.StatusOK, item)
}
func (h *Handler) AgentSyncBundle(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Gateway-Bootstrap-Token") != h.bootstrapToken {
apiutil.Error(w, http.StatusUnauthorized, "unauthorized", "invalid gateway bootstrap token")
return
}
bundle, err := h.service.BuildSyncBundle(r.Context(), chi.URLParam(r, "id"))
if err != nil {
apiutil.Error(w, http.StatusBadRequest, "gateway_sync_failed", "unable to build sync bundle")
return
}
apiutil.JSON(w, http.StatusOK, bundle)
}
func (h *Handler) Telemetry(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Gateway-Bootstrap-Token") != h.bootstrapToken {
apiutil.Error(w, http.StatusUnauthorized, "unauthorized", "invalid gateway bootstrap token")
return
}
var input TelemetrySnapshot
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
apiutil.Error(w, http.StatusBadRequest, "invalid_json", "invalid request body")
return
}
if err := h.service.StoreTelemetry(r.Context(), chi.URLParam(r, "id"), input); err != nil {
apiutil.Error(w, http.StatusBadRequest, "gateway_telemetry_failed", "unable to store gateway telemetry")
return
}
apiutil.JSON(w, http.StatusOK, map[string]any{"ok": true})
}