130 lines
4.3 KiB
Go
130 lines
4.3 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"admin-console/internal/metrics"
|
|
"admin-console/internal/model"
|
|
)
|
|
|
|
// Server — HTTP-сервер admin-console.
|
|
type Server struct {
|
|
adminToken string
|
|
k8s *metrics.K8sClient
|
|
}
|
|
|
|
func NewServer(adminToken string) *Server {
|
|
k8s, err := metrics.NewK8sClient()
|
|
if err != nil {
|
|
// вне кластера — работаем без k8s (для локальной разработки)
|
|
k8s = nil
|
|
}
|
|
return &Server{adminToken: adminToken, k8s: k8s}
|
|
}
|
|
|
|
func (s *Server) RegisterRoutes(mux *http.ServeMux) {
|
|
// статика UI
|
|
mux.Handle("/admin/", http.StripPrefix("/admin/", http.FileServer(http.Dir("ui"))))
|
|
|
|
// API
|
|
mux.HandleFunc("/admin/api/health", s.handleHealth)
|
|
mux.HandleFunc("/admin/api/namespaces", s.authMiddleware(s.handleNamespaces))
|
|
mux.HandleFunc("/admin/api/namespaces/", s.authMiddleware(s.handleNamespaceDetail))
|
|
mux.HandleFunc("/admin/api/usage", s.authMiddleware(s.handleUsage))
|
|
mux.HandleFunc("/admin/api/users", s.authMiddleware(s.handleUsers))
|
|
}
|
|
|
|
// authMiddleware проверяет Bearer-токен из заголовка Authorization.
|
|
func (s *Server) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
auth := r.Header.Get("Authorization")
|
|
if !strings.HasPrefix(auth, "Bearer ") || strings.TrimPrefix(auth, "Bearer ") != s.adminToken {
|
|
writeJSON(w, http.StatusUnauthorized, model.ErrResp("unauthorized"))
|
|
return
|
|
}
|
|
next(w, r)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
}
|
|
|
|
// handleNamespaces возвращает список клиентских namespace-ов (быстро, без счётчиков).
|
|
func (s *Server) handleNamespaces(w http.ResponseWriter, r *http.Request) {
|
|
if s.k8s == nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, model.ErrResp("k8s unavailable"))
|
|
return
|
|
}
|
|
ns, err := s.k8s.ListClientNamespaces(r.Context())
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, model.ErrResp(err.Error()))
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"namespaces": ns})
|
|
}
|
|
|
|
// handleNamespaceDetail возвращает детальную информацию об одном namespace (с CRD-счётчиками).
|
|
func (s *Server) handleNamespaceDetail(w http.ResponseWriter, r *http.Request) {
|
|
if s.k8s == nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, model.ErrResp("k8s unavailable"))
|
|
return
|
|
}
|
|
// URL: /admin/api/namespaces/{name}
|
|
name := strings.TrimPrefix(r.URL.Path, "/admin/api/namespaces/")
|
|
if name == "" {
|
|
writeJSON(w, http.StatusBadRequest, model.ErrResp("missing namespace name"))
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
|
defer cancel()
|
|
info, err := s.k8s.GetNamespaceDetail(ctx, name)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusNotFound, model.ErrResp(err.Error()))
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, info)
|
|
}
|
|
|
|
// handleUsage возвращает агрегированные метрики использования для бухгалтерии.
|
|
func (s *Server) handleUsage(w http.ResponseWriter, r *http.Request) {
|
|
if s.k8s == nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, model.ErrResp("k8s unavailable"))
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second)
|
|
defer cancel()
|
|
usage, err := s.k8s.CollectUsage(ctx)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, model.ErrResp(err.Error()))
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"usage": usage})
|
|
}
|
|
|
|
// handleUsers возвращает список пользователей из secrets в клиентских namespace-ах.
|
|
func (s *Server) handleUsers(w http.ResponseWriter, r *http.Request) {
|
|
if s.k8s == nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, model.ErrResp("k8s unavailable"))
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
|
defer cancel()
|
|
users, err := s.k8s.ListUsers(ctx)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, model.ErrResp(err.Error()))
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"users": users})
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, v any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(v)
|
|
}
|