admin-console: fast namespace list + async detail loading per ns
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"admin-console/internal/metrics"
|
||||
"admin-console/internal/model"
|
||||
@@ -31,6 +33,7 @@ func (s *Server) RegisterRoutes(mux *http.ServeMux) {
|
||||
// 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))
|
||||
}
|
||||
@@ -51,7 +54,7 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// handleNamespaces возвращает список клиентских namespace-ов с их метриками.
|
||||
// handleNamespaces возвращает список клиентских namespace-ов (быстро, без счётчиков).
|
||||
func (s *Server) handleNamespaces(w http.ResponseWriter, r *http.Request) {
|
||||
if s.k8s == nil {
|
||||
writeJSON(w, http.StatusServiceUnavailable, model.ErrResp("k8s unavailable"))
|
||||
@@ -65,13 +68,37 @@ func (s *Server) handleNamespaces(w http.ResponseWriter, r *http.Request) {
|
||||
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
|
||||
}
|
||||
usage, err := s.k8s.CollectUsage(r.Context())
|
||||
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
|
||||
@@ -85,7 +112,9 @@ func (s *Server) handleUsers(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusServiceUnavailable, model.ErrResp("k8s unavailable"))
|
||||
return
|
||||
}
|
||||
users, err := s.k8s.ListUsers(r.Context())
|
||||
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
|
||||
|
||||
@@ -96,8 +96,7 @@ func (k *K8sClient) initS3() {
|
||||
log.Printf("[admin-console] S3 client ready, bucket=%s", bucket)
|
||||
}
|
||||
|
||||
// ListClientNamespaces возвращает namespace-ы с лейблом managed-by=fission-console.
|
||||
// Для каждого считает количество объектов Fission CRD через Dynamic клиент.
|
||||
// ListClientNamespaces возвращает список namespace-ов БЕЗ CRD-счётчиков (быстро).
|
||||
func (k *K8sClient) ListClientNamespaces(ctx context.Context) ([]model.NamespaceInfo, error) {
|
||||
nsList, err := k.cs.CoreV1().Namespaces().List(ctx, metav1.ListOptions{
|
||||
LabelSelector: "managed-by=fission-console",
|
||||
@@ -108,17 +107,30 @@ func (k *K8sClient) ListClientNamespaces(ctx context.Context) ([]model.Namespace
|
||||
|
||||
result := make([]model.NamespaceInfo, 0, len(nsList.Items))
|
||||
for _, ns := range nsList.Items {
|
||||
info := model.NamespaceInfo{
|
||||
result = append(result, model.NamespaceInfo{
|
||||
Name: ns.Name,
|
||||
UserEmail: ns.Labels["user"],
|
||||
CreatedAt: ns.CreationTimestamp.Format(time.RFC3339),
|
||||
}
|
||||
k.fillCRDCounts(ctx, ns.Name, &info)
|
||||
result = append(result, info)
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetNamespaceDetail возвращает информацию об одном namespace с CRD-счётчиками.
|
||||
func (k *K8sClient) GetNamespaceDetail(ctx context.Context, nsName string) (*model.NamespaceInfo, error) {
|
||||
ns, err := k.cs.CoreV1().Namespaces().Get(ctx, nsName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info := &model.NamespaceInfo{
|
||||
Name: ns.Name,
|
||||
UserEmail: ns.Labels["user"],
|
||||
CreatedAt: ns.CreationTimestamp.Format(time.RFC3339),
|
||||
}
|
||||
k.fillCRDCounts(ctx, nsName, info)
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// fillCRDCounts заполняет счётчики Fission-ресурсов для одного namespace.
|
||||
func (k *K8sClient) fillCRDCounts(ctx context.Context, ns string, info *model.NamespaceInfo) {
|
||||
count := func(gvr schema.GroupVersionResource) int {
|
||||
@@ -126,8 +138,6 @@ func (k *K8sClient) fillCRDCounts(ctx context.Context, ns string, info *model.Na
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
// List возвращает items и list-metadata с remaining count.
|
||||
// Используем оставшийся счёт для точности.
|
||||
if rem := list.GetRemainingItemCount(); rem != nil {
|
||||
return int(*rem) + len(list.Items)
|
||||
}
|
||||
@@ -222,10 +232,8 @@ func (k *K8sClient) ListUsers(ctx context.Context) ([]model.UserInfo, error) {
|
||||
|
||||
var users []model.UserInfo
|
||||
for _, ns := range nsList.Items {
|
||||
// label user содержит email владельца namespace
|
||||
email := ns.Labels["user"]
|
||||
if email == "" {
|
||||
// fallback: попробовать прочитать из secret
|
||||
secret, err := k.cs.CoreV1().Secrets(ns.Name).Get(ctx, "fission-console-users", metav1.GetOptions{})
|
||||
if err == nil {
|
||||
for key := range secret.Data {
|
||||
|
||||
Reference in New Issue
Block a user