diff --git a/admin-console/Dockerfile b/admin-console/Dockerfile new file mode 100644 index 0000000..4312586 --- /dev/null +++ b/admin-console/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.26-alpine AS builder +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o admin-console ./cmd/server/ + +FROM alpine:3.20 +RUN apk add --no-cache ca-certificates +COPY --from=builder /build/admin-console /admin-console +COPY ui/ /ui/ +EXPOSE 8091 +ENTRYPOINT ["/admin-console"] diff --git a/admin-console/cmd/server/main.go b/admin-console/cmd/server/main.go new file mode 100644 index 0000000..24a96d4 --- /dev/null +++ b/admin-console/cmd/server/main.go @@ -0,0 +1,28 @@ +package main +package main + +import ( + "log" + "net/http" + "os" + + "admin-console/internal/api" +) + +func main() { + adminToken := os.Getenv("ADMIN_TOKEN") + if adminToken == "" { + log.Fatal("ADMIN_TOKEN env variable is required") + } + + srv := api.NewServer(adminToken) + + mux := http.NewServeMux() + srv.RegisterRoutes(mux) + + addr := ":8091" + log.Printf("admin-console starting on %s", addr) + if err := http.ListenAndServe(addr, mux); err != nil { + log.Fatalf("server error: %v", err) + } +} diff --git a/admin-console/deploy/admin.yaml b/admin-console/deploy/admin.yaml new file mode 100644 index 0000000..84e2383 --- /dev/null +++ b/admin-console/deploy/admin.yaml @@ -0,0 +1,81 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: fission-admin-console + namespace: fission +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: fission-admin-console +rules: + # читать ВСЕ namespaces (для отображения клиентов) + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list"] + # читать секреты в клиентских namespaces (для списка пользователей) + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list"] + # читать pods (для метрик потребления) + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list"] + # читать Fission CRD + - apiGroups: ["fission.io"] + resources: ["environments", "packages", "functions", "httptriggers", "timetriggers"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: fission-admin-console +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: fission-admin-console +subjects: + - kind: ServiceAccount + name: fission-admin-console + namespace: fission +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fission-admin-console + namespace: fission +spec: + replicas: 1 + selector: + matchLabels: + app: fission-admin-console + template: + metadata: + labels: + app: fission-admin-console + spec: + serviceAccountName: fission-admin-console + containers: + - name: fission-admin-console + image: naeel/fission-admin-console:v0.1.0 + imagePullPolicy: Always + ports: + - containerPort: 8091 + env: + - name: ADMIN_TOKEN + valueFrom: + secretKeyRef: + name: fission-admin-token + key: token +--- +apiVersion: v1 +kind: Service +metadata: + name: fission-admin-console + namespace: fission +spec: + selector: + app: fission-admin-console + ports: + - port: 8091 + targetPort: 8091 diff --git a/admin-console/go.mod b/admin-console/go.mod new file mode 100644 index 0000000..a899a47 --- /dev/null +++ b/admin-console/go.mod @@ -0,0 +1,32 @@ +module admin-console + +go 1.25.0 + +require ( + k8s.io/client-go v0.31.0 + k8s.io/apimachinery v0.31.0 + k8s.io/api v0.31.0 +) + +require ( + github.com/go-logr/logr v1.4.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/term v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + sigs.k8s.io/json v0.0.0-20221116144056-70938f8a5b36 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/admin-console/internal/api/server.go b/admin-console/internal/api/server.go new file mode 100644 index 0000000..00f3b0c --- /dev/null +++ b/admin-console/internal/api/server.go @@ -0,0 +1,100 @@ +package api + +import ( + "encoding/json" + "net/http" + "strings" + + "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/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}) +} + +// 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()) + 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 + } + users, err := s.k8s.ListUsers(r.Context()) + 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) +} diff --git a/admin-console/internal/metrics/k8s.go b/admin-console/internal/metrics/k8s.go new file mode 100644 index 0000000..7d80ea8 --- /dev/null +++ b/admin-console/internal/metrics/k8s.go @@ -0,0 +1,121 @@ +package metrics + +import ( + "context" + "fmt" + "strings" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "admin-console/internal/model" +) + +// K8sClient — обёртка над kubernetes.Clientset для нужд admin-console. +type K8sClient struct { + cs *kubernetes.Clientset +} + +// NewK8sClient создаёт клиента из in-cluster конфига, с fallback на ~/.kube/config. +func NewK8sClient() (*K8sClient, error) { + cfg, err := rest.InClusterConfig() + if err != nil { + cfg, err = clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile) + if err != nil { + return nil, fmt.Errorf("no k8s config: %w", err) + } + } + cs, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, err + } + return &K8sClient{cs: cs}, nil +} + +// ListClientNamespaces возвращает namespace-ы с лейблом managed-by=fission-console. +// Для каждого считает количество объектов Fission CRD через Dynamic клиент +// (пока упрощённо — только через secrets и labels). +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", + }) + if err != nil { + return nil, err + } + + result := make([]model.NamespaceInfo, 0, len(nsList.Items)) + for _, ns := range nsList.Items { + info := model.NamespaceInfo{ + Name: ns.Name, + UserEmail: ns.Labels["user"], + CreatedAt: ns.CreationTimestamp.Format(time.RFC3339), + } + // TODO: добавить подсчёт Fission CRD через dynamic client + result = append(result, info) + } + return result, nil +} + +// CollectUsage возвращает агрегированную статистику по всем клиентским namespace-ам. +func (k *K8sClient) CollectUsage(ctx context.Context) ([]model.UsageEntry, error) { + nsList, err := k.cs.CoreV1().Namespaces().List(ctx, metav1.ListOptions{ + LabelSelector: "managed-by=fission-console", + }) + if err != nil { + return nil, err + } + + now := time.Now().UTC() + startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC) + + result := make([]model.UsageEntry, 0, len(nsList.Items)) + for _, ns := range nsList.Items { + entry := model.UsageEntry{ + Namespace: ns.Name, + UserEmail: ns.Labels["user"], + PeriodStart: startOfMonth.Format("2006-01-02"), + PeriodEnd: now.Format("2006-01-02"), + } + // TODO: заполнить реальными данными из Prometheus / metrics-server / S3 + result = append(result, entry) + } + return result, nil +} + +// ListUsers собирает пользователей из secret fission-console-users в каждом +// клиентском namespace. +func (k *K8sClient) ListUsers(ctx context.Context) ([]model.UserInfo, error) { + nsList, err := k.cs.CoreV1().Namespaces().List(ctx, metav1.ListOptions{ + LabelSelector: "managed-by=fission-console", + }) + if err != nil { + return nil, err + } + + 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 { + if strings.Contains(key, "@") { + email = key + break + } + } + } + } + users = append(users, model.UserInfo{ + Email: email, + Namespace: ns.Name, + CreatedAt: ns.CreationTimestamp.Format(time.RFC3339), + }) + } + return users, nil +} diff --git a/admin-console/internal/model/types.go b/admin-console/internal/model/types.go new file mode 100644 index 0000000..ed9f254 --- /dev/null +++ b/admin-console/internal/model/types.go @@ -0,0 +1,40 @@ +package model + +// ErrResp — стандартный ответ с ошибкой. +func ErrResp(msg string) map[string]string { + return map[string]string{"error": msg} +} + +// NamespaceInfo — информация об одном клиентском namespace. +type NamespaceInfo struct { + Name string `json:"name"` + UserEmail string `json:"user_email,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + + // счётчики ресурсов Fission + Functions int `json:"functions"` + Packages int `json:"packages"` + Environments int `json:"environments"` + HTTPTriggers int `json:"http_triggers"` + TimeTriggers int `json:"time_triggers"` +} + +// UsageEntry — строка использования для одного namespace (для бухгалтерии). +type UsageEntry struct { + Namespace string `json:"namespace"` + UserEmail string `json:"user_email,omitempty"` + Functions int `json:"functions"` + Invocations int64 `json:"invocations_total"` // TODO: из Prometheus/logs + StorageGB float64 `json:"storage_gb"` // TODO: из storagesvc/S3 + CPUMilliCores int64 `json:"cpu_millicores"` // TODO: из metrics-server + MemoryMB int64 `json:"memory_mb"` // TODO: из metrics-server + PeriodStart string `json:"period_start"` + PeriodEnd string `json:"period_end"` +} + +// UserInfo — пользователь из secret fission-console-users. +type UserInfo struct { + Email string `json:"email"` + Namespace string `json:"namespace"` + CreatedAt string `json:"created_at,omitempty"` +} diff --git a/admin-console/ui/index.html b/admin-console/ui/index.html new file mode 100644 index 0000000..e0a933e --- /dev/null +++ b/admin-console/ui/index.html @@ -0,0 +1,154 @@ + + + + + + Fission Admin Console v0.1.0 + + + +
+

Fission Admin Console

+ ADMIN +
+ +
+
+

Вход в Admin Console

+ + + +
+
+ +
+ +
+
+ + + + diff --git a/console/Dockerfile b/client-console/Dockerfile similarity index 100% rename from console/Dockerfile rename to client-console/Dockerfile diff --git a/console/cmd/server/main.go b/client-console/cmd/server/main.go similarity index 100% rename from console/cmd/server/main.go rename to client-console/cmd/server/main.go diff --git a/console/deploy/console.yaml b/client-console/deploy/console.yaml similarity index 100% rename from console/deploy/console.yaml rename to client-console/deploy/console.yaml diff --git a/console/go.mod b/client-console/go.mod similarity index 100% rename from console/go.mod rename to client-console/go.mod diff --git a/console/go.sum b/client-console/go.sum similarity index 100% rename from console/go.sum rename to client-console/go.sum diff --git a/console/internal/api/ai_check.go b/client-console/internal/api/ai_check.go similarity index 100% rename from console/internal/api/ai_check.go rename to client-console/internal/api/ai_check.go diff --git a/console/internal/api/ai_prompts.go b/client-console/internal/api/ai_prompts.go similarity index 100% rename from console/internal/api/ai_prompts.go rename to client-console/internal/api/ai_prompts.go diff --git a/console/internal/api/ai_prompts_test.go b/client-console/internal/api/ai_prompts_test.go similarity index 100% rename from console/internal/api/ai_prompts_test.go rename to client-console/internal/api/ai_prompts_test.go diff --git a/console/internal/api/auth.go b/client-console/internal/api/auth.go similarity index 100% rename from console/internal/api/auth.go rename to client-console/internal/api/auth.go diff --git a/console/internal/api/cron_metrics.go b/client-console/internal/api/cron_metrics.go similarity index 100% rename from console/internal/api/cron_metrics.go rename to client-console/internal/api/cron_metrics.go diff --git a/console/internal/api/doc.go b/client-console/internal/api/doc.go similarity index 100% rename from console/internal/api/doc.go rename to client-console/internal/api/doc.go diff --git a/console/internal/api/explain_archive.go b/client-console/internal/api/explain_archive.go similarity index 100% rename from console/internal/api/explain_archive.go rename to client-console/internal/api/explain_archive.go diff --git a/console/internal/api/function_archive.go b/client-console/internal/api/function_archive.go similarity index 100% rename from console/internal/api/function_archive.go rename to client-console/internal/api/function_archive.go diff --git a/console/internal/api/function_clone.go b/client-console/internal/api/function_clone.go similarity index 100% rename from console/internal/api/function_clone.go rename to client-console/internal/api/function_clone.go diff --git a/console/internal/api/function_clone_test.go b/client-console/internal/api/function_clone_test.go similarity index 100% rename from console/internal/api/function_clone_test.go rename to client-console/internal/api/function_clone_test.go diff --git a/console/internal/api/function_code.go b/client-console/internal/api/function_code.go similarity index 100% rename from console/internal/api/function_code.go rename to client-console/internal/api/function_code.go diff --git a/console/internal/api/function_crud.go b/client-console/internal/api/function_crud.go similarity index 100% rename from console/internal/api/function_crud.go rename to client-console/internal/api/function_crud.go diff --git a/console/internal/api/function_invoke.go b/client-console/internal/api/function_invoke.go similarity index 100% rename from console/internal/api/function_invoke.go rename to client-console/internal/api/function_invoke.go diff --git a/console/internal/api/function_timestamps.go b/client-console/internal/api/function_timestamps.go similarity index 100% rename from console/internal/api/function_timestamps.go rename to client-console/internal/api/function_timestamps.go diff --git a/console/internal/api/function_timestamps_test.go b/client-console/internal/api/function_timestamps_test.go similarity index 100% rename from console/internal/api/function_timestamps_test.go rename to client-console/internal/api/function_timestamps_test.go diff --git a/console/internal/api/handlers.go b/client-console/internal/api/handlers.go similarity index 100% rename from console/internal/api/handlers.go rename to client-console/internal/api/handlers.go diff --git a/console/internal/api/kwtriggers.go b/client-console/internal/api/kwtriggers.go similarity index 100% rename from console/internal/api/kwtriggers.go rename to client-console/internal/api/kwtriggers.go diff --git a/console/internal/api/lint_archive.go b/client-console/internal/api/lint_archive.go similarity index 100% rename from console/internal/api/lint_archive.go rename to client-console/internal/api/lint_archive.go diff --git a/console/internal/api/middleware.go b/client-console/internal/api/middleware.go similarity index 100% rename from console/internal/api/middleware.go rename to client-console/internal/api/middleware.go diff --git a/console/internal/api/mqtriggers.go b/client-console/internal/api/mqtriggers.go similarity index 100% rename from console/internal/api/mqtriggers.go rename to client-console/internal/api/mqtriggers.go diff --git a/console/internal/api/ns_status.go b/client-console/internal/api/ns_status.go similarity index 100% rename from console/internal/api/ns_status.go rename to client-console/internal/api/ns_status.go diff --git a/console/internal/api/package.go b/client-console/internal/api/package.go similarity index 100% rename from console/internal/api/package.go rename to client-console/internal/api/package.go diff --git a/console/internal/api/package_test.go b/client-console/internal/api/package_test.go similarity index 100% rename from console/internal/api/package_test.go rename to client-console/internal/api/package_test.go diff --git a/console/internal/api/server.go b/client-console/internal/api/server.go similarity index 100% rename from console/internal/api/server.go rename to client-console/internal/api/server.go diff --git a/console/internal/api/stats.go b/client-console/internal/api/stats.go similarity index 100% rename from console/internal/api/stats.go rename to client-console/internal/api/stats.go diff --git a/console/internal/api/status.go b/client-console/internal/api/status.go similarity index 100% rename from console/internal/api/status.go rename to client-console/internal/api/status.go diff --git a/console/internal/api/storagesvc.go b/client-console/internal/api/storagesvc.go similarity index 100% rename from console/internal/api/storagesvc.go rename to client-console/internal/api/storagesvc.go diff --git a/console/internal/api/timetriggers.go b/client-console/internal/api/timetriggers.go similarity index 100% rename from console/internal/api/timetriggers.go rename to client-console/internal/api/timetriggers.go diff --git a/console/internal/api/timetriggers_test.go b/client-console/internal/api/timetriggers_test.go similarity index 100% rename from console/internal/api/timetriggers_test.go rename to client-console/internal/api/timetriggers_test.go diff --git a/console/internal/auth/auth.go b/client-console/internal/auth/auth.go similarity index 100% rename from console/internal/auth/auth.go rename to client-console/internal/auth/auth.go diff --git a/console/internal/auth/auth_test.go b/client-console/internal/auth/auth_test.go similarity index 100% rename from console/internal/auth/auth_test.go rename to client-console/internal/auth/auth_test.go diff --git a/console/internal/billing/billing.go b/client-console/internal/billing/billing.go similarity index 100% rename from console/internal/billing/billing.go rename to client-console/internal/billing/billing.go diff --git a/console/internal/billing/factory.go b/client-console/internal/billing/factory.go similarity index 100% rename from console/internal/billing/factory.go rename to client-console/internal/billing/factory.go diff --git a/console/internal/billing/noop.go b/client-console/internal/billing/noop.go similarity index 100% rename from console/internal/billing/noop.go rename to client-console/internal/billing/noop.go diff --git a/console/internal/billing/pg.go b/client-console/internal/billing/pg.go similarity index 100% rename from console/internal/billing/pg.go rename to client-console/internal/billing/pg.go diff --git a/console/internal/cloud/doc.go b/client-console/internal/cloud/doc.go similarity index 100% rename from console/internal/cloud/doc.go rename to client-console/internal/cloud/doc.go diff --git a/console/internal/cloud/network.go b/client-console/internal/cloud/network.go similarity index 100% rename from console/internal/cloud/network.go rename to client-console/internal/cloud/network.go diff --git a/console/internal/cloud/quota.go b/client-console/internal/cloud/quota.go similarity index 100% rename from console/internal/cloud/quota.go rename to client-console/internal/cloud/quota.go diff --git a/console/internal/cloud/tenant.go b/client-console/internal/cloud/tenant.go similarity index 100% rename from console/internal/cloud/tenant.go rename to client-console/internal/cloud/tenant.go diff --git a/console/internal/fission/client.go b/client-console/internal/fission/client.go similarity index 100% rename from console/internal/fission/client.go rename to client-console/internal/fission/client.go diff --git a/console/internal/fission/environment.go b/client-console/internal/fission/environment.go similarity index 100% rename from console/internal/fission/environment.go rename to client-console/internal/fission/environment.go diff --git a/console/internal/fission/namespace.go b/client-console/internal/fission/namespace.go similarity index 100% rename from console/internal/fission/namespace.go rename to client-console/internal/fission/namespace.go diff --git a/console/internal/model/types.go b/client-console/internal/model/types.go similarity index 100% rename from console/internal/model/types.go rename to client-console/internal/model/types.go diff --git a/console/internal/runtime/doc.go b/client-console/internal/runtime/doc.go similarity index 100% rename from console/internal/runtime/doc.go rename to client-console/internal/runtime/doc.go diff --git a/console/internal/runtime/entrypoint.go b/client-console/internal/runtime/entrypoint.go similarity index 100% rename from console/internal/runtime/entrypoint.go rename to client-console/internal/runtime/entrypoint.go diff --git a/console/internal/runtime/go.go b/client-console/internal/runtime/go.go similarity index 100% rename from console/internal/runtime/go.go rename to client-console/internal/runtime/go.go diff --git a/console/internal/runtime/helpers.go b/client-console/internal/runtime/helpers.go similarity index 100% rename from console/internal/runtime/helpers.go rename to client-console/internal/runtime/helpers.go diff --git a/console/internal/runtime/nodejs.go b/client-console/internal/runtime/nodejs.go similarity index 100% rename from console/internal/runtime/nodejs.go rename to client-console/internal/runtime/nodejs.go diff --git a/console/internal/runtime/nodejs_test.go b/client-console/internal/runtime/nodejs_test.go similarity index 100% rename from console/internal/runtime/nodejs_test.go rename to client-console/internal/runtime/nodejs_test.go diff --git a/console/internal/runtime/script.go b/client-console/internal/runtime/script.go similarity index 100% rename from console/internal/runtime/script.go rename to client-console/internal/runtime/script.go diff --git a/console/internal/stats/factory.go b/client-console/internal/stats/factory.go similarity index 100% rename from console/internal/stats/factory.go rename to client-console/internal/stats/factory.go diff --git a/console/internal/stats/grafana.go b/client-console/internal/stats/grafana.go similarity index 100% rename from console/internal/stats/grafana.go rename to client-console/internal/stats/grafana.go diff --git a/console/internal/stats/noop.go b/client-console/internal/stats/noop.go similarity index 100% rename from console/internal/stats/noop.go rename to client-console/internal/stats/noop.go diff --git a/console/internal/stats/provider.go b/client-console/internal/stats/provider.go similarity index 100% rename from console/internal/stats/provider.go rename to client-console/internal/stats/provider.go diff --git a/console/ui/cron.go b/client-console/ui/cron.go similarity index 100% rename from console/ui/cron.go rename to client-console/ui/cron.go diff --git a/console/ui/cron.html b/client-console/ui/cron.html similarity index 100% rename from console/ui/cron.html rename to client-console/ui/cron.html diff --git a/console/ui/css/styles.css b/client-console/ui/css/styles.css similarity index 100% rename from console/ui/css/styles.css rename to client-console/ui/css/styles.css diff --git a/console/ui/embed.go b/client-console/ui/embed.go similarity index 100% rename from console/ui/embed.go rename to client-console/ui/embed.go diff --git a/console/ui/index.html b/client-console/ui/index.html similarity index 100% rename from console/ui/index.html rename to client-console/ui/index.html diff --git a/console/ui/js/ai.js b/client-console/ui/js/ai.js similarity index 100% rename from console/ui/js/ai.js rename to client-console/ui/js/ai.js diff --git a/console/ui/js/api.js b/client-console/ui/js/api.js similarity index 100% rename from console/ui/js/api.js rename to client-console/ui/js/api.js diff --git a/console/ui/js/app.js b/client-console/ui/js/app.js similarity index 100% rename from console/ui/js/app.js rename to client-console/ui/js/app.js diff --git a/console/ui/js/auth.js b/client-console/ui/js/auth.js similarity index 100% rename from console/ui/js/auth.js rename to client-console/ui/js/auth.js diff --git a/console/ui/js/config.js b/client-console/ui/js/config.js similarity index 100% rename from console/ui/js/config.js rename to client-console/ui/js/config.js diff --git a/console/ui/js/fn-archive.js b/client-console/ui/js/fn-archive.js similarity index 100% rename from console/ui/js/fn-archive.js rename to client-console/ui/js/fn-archive.js diff --git a/console/ui/js/fn-code.js b/client-console/ui/js/fn-code.js similarity index 100% rename from console/ui/js/fn-code.js rename to client-console/ui/js/fn-code.js diff --git a/console/ui/js/functions.js b/client-console/ui/js/functions.js similarity index 100% rename from console/ui/js/functions.js rename to client-console/ui/js/functions.js diff --git a/console/ui/js/init.js b/client-console/ui/js/init.js similarity index 100% rename from console/ui/js/init.js rename to client-console/ui/js/init.js diff --git a/console/ui/js/modals.js b/client-console/ui/js/modals.js similarity index 100% rename from console/ui/js/modals.js rename to client-console/ui/js/modals.js diff --git a/console/ui/js/mq.js b/client-console/ui/js/mq.js similarity index 100% rename from console/ui/js/mq.js rename to client-console/ui/js/mq.js diff --git a/console/ui/js/triggers.js b/client-console/ui/js/triggers.js similarity index 100% rename from console/ui/js/triggers.js rename to client-console/ui/js/triggers.js diff --git a/console/ui/js/ui.js b/client-console/ui/js/ui.js similarity index 100% rename from console/ui/js/ui.js rename to client-console/ui/js/ui.js diff --git a/AUDIT_CONSOLE_FUNCTIONS.md b/doc/AUDIT_CONSOLE_FUNCTIONS.md similarity index 100% rename from AUDIT_CONSOLE_FUNCTIONS.md rename to doc/AUDIT_CONSOLE_FUNCTIONS.md diff --git a/FISSION_IMAGES.md b/doc/FISSION_IMAGES.md similarity index 100% rename from FISSION_IMAGES.md rename to doc/FISSION_IMAGES.md diff --git a/FIXES_APPLIED.md b/doc/FIXES_APPLIED.md similarity index 100% rename from FIXES_APPLIED.md rename to doc/FIXES_APPLIED.md diff --git a/FIX_401_INVOKE.md b/doc/FIX_401_INVOKE.md similarity index 100% rename from FIX_401_INVOKE.md rename to doc/FIX_401_INVOKE.md diff --git a/FIX_ARCHIVE_PRUNER.md b/doc/FIX_ARCHIVE_PRUNER.md similarity index 100% rename from FIX_ARCHIVE_PRUNER.md rename to doc/FIX_ARCHIVE_PRUNER.md diff --git a/LAYER_EXPLANATION_RU.md b/doc/LAYER_EXPLANATION_RU.md similarity index 100% rename from LAYER_EXPLANATION_RU.md rename to doc/LAYER_EXPLANATION_RU.md diff --git a/NEXT_CHAT_LAYER2_API.md b/doc/NEXT_CHAT_LAYER2_API.md similarity index 100% rename from NEXT_CHAT_LAYER2_API.md rename to doc/NEXT_CHAT_LAYER2_API.md diff --git a/TODO.txt b/doc/TODO.txt similarity index 100% rename from TODO.txt rename to doc/TODO.txt diff --git a/fission-cli-compat-report-2026-05-04.md b/doc/fission-cli-compat-report-2026-05-04.md similarity index 100% rename from fission-cli-compat-report-2026-05-04.md rename to doc/fission-cli-compat-report-2026-05-04.md diff --git a/misc/HANDOFF_LYNGVO.md b/misc/HANDOFF_LYNGVO.md new file mode 100644 index 0000000..0bb4f95 --- /dev/null +++ b/misc/HANDOFF_LYNGVO.md @@ -0,0 +1,46 @@ +# Lyngvo — AI Pronunciation Trainer (Italian) MVP + +## Концепция + +Веб-приложение для тренировки итальянского произношения: + +1. Пользователь вводит текст (слово/фразу) +2. Приложение синтезирует эталон (TTS) +3. Пользователь записывает свой голос (MediaRecorder) +4. Приложение транскрибирует запись (Whisper), сравнивает с оригиналом +5. Показывает: similarity score, выделяет пропущенные/лишние слова +6. Воспроизводит target → пауза → user (stereo опционально) + +## Tech stack (утверждён) + +- **Frontend:** HTML + JS (чистый, без React) +- **Audio:** Web Audio API, MediaRecorder API +- **TTS:** Web Speech API (`speechSynthesis`) — бесплатно, итальянский голос есть +- **STT:** требуется внешний Whisper API (aillm.ru не поддерживает) +- **Бэкенд:** отсутствует (MVP — только статика) +- **DevOps:** minikube / k8s кластер `5.172.178.213`, nginx-ingress + +## Что уже сделано + +- DNS: `lang.kube5s.ru` A → `5.172.178.213` +- Проект будет в отдельной репе (пока не создана) +- Модель aillm.ru: `gpt-oss-120b` — только текст, STT/TTS не поддерживаются + +## Что делать + +1. Создать репу (локально `/home/naeel/lyngvo/` + Gitea на ВМ) +2. Развернуть HTML-приложение в кластере (nginx-pod + ingress) +3. Реализовать UI: поле ввода, кнопки Generate / Play TTS / Record / Compare +4. TTS через Web Speech API +5. Запись через MediaRecorder → blob → отправить на Whisper API +6. Сравнение текстов (similarity score, highlight diff) +7. Playback: target → 300-800ms пауза → user +8. Опционально: stereo (left=target, right=user) +9. Настроить HTTPS для `lang.kube5s.ru` (cert-manager / Let's Encrypt) + +## Контакты/доступ + +- ВМ: `naeel@5.172.178.213` ключ `~/.ssh/naeel_vm_id_ed25519` +- Кластер: kubectl (там же на ВМ) +- S3: `sless-functions` на `s3.msk-1.ngcloud.ru` +- AI ключ: `sk-ucI5YvOticoOQ9Kuj5K9mQ` (aillm.ru, только текст) diff --git a/cleanup_test_namespaces.sh b/misc/cleanup_test_namespaces.sh similarity index 100% rename from cleanup_test_namespaces.sh rename to misc/cleanup_test_namespaces.sh diff --git a/demo-fn.zip b/misc/demo-fn.zip similarity index 100% rename from demo-fn.zip rename to misc/demo-fn.zip diff --git a/demo.py b/misc/demo.py similarity index 100% rename from demo.py rename to misc/demo.py diff --git a/demo.zip b/misc/demo.zip similarity index 100% rename from demo.zip rename to misc/demo.zip diff --git a/ellipse_126x91mm.svg b/misc/ellipse_126x91mm.svg similarity index 100% rename from ellipse_126x91mm.svg rename to misc/ellipse_126x91mm.svg diff --git a/ellipse_126x91mm_a4centered.svg b/misc/ellipse_126x91mm_a4centered.svg similarity index 100% rename from ellipse_126x91mm_a4centered.svg rename to misc/ellipse_126x91mm_a4centered.svg diff --git a/plan.md b/misc/plan.md similarity index 100% rename from plan.md rename to misc/plan.md diff --git a/requirements.txt b/misc/requirements.txt similarity index 100% rename from requirements.txt rename to misc/requirements.txt diff --git a/unroll_ring.svg b/misc/unroll_ring.svg similarity index 100% rename from unroll_ring.svg rename to misc/unroll_ring.svg diff --git a/unroll_ring_correct.svg b/misc/unroll_ring_correct.svg similarity index 100% rename from unroll_ring_correct.svg rename to misc/unroll_ring_correct.svg diff --git a/unroll_ring_params.txt b/misc/unroll_ring_params.txt similarity index 100% rename from unroll_ring_params.txt rename to misc/unroll_ring_params.txt diff --git a/unroll_ring_part1.svg b/misc/unroll_ring_part1.svg similarity index 100% rename from unroll_ring_part1.svg rename to misc/unroll_ring_part1.svg diff --git a/unroll_ring_part2.svg b/misc/unroll_ring_part2.svg similarity index 100% rename from unroll_ring_part2.svg rename to misc/unroll_ring_part2.svg diff --git a/unroll_ring_sine.svg b/misc/unroll_ring_sine.svg similarity index 100% rename from unroll_ring_sine.svg rename to misc/unroll_ring_sine.svg diff --git a/misc/Описание API _ Dosc.pdf b/misc/Описание API _ Dosc.pdf new file mode 100755 index 0000000..a8c1dfc Binary files /dev/null and b/misc/Описание API _ Dosc.pdf differ