165 lines
5.2 KiB
Go
165 lines
5.2 KiB
Go
package web
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"ymhut-box/server/unified-management/internal/config"
|
|
"ymhut-box/server/unified-management/internal/db"
|
|
"ymhut-box/server/unified-management/internal/health"
|
|
)
|
|
|
|
func (r *router) handleAdminDatabase(w http.ResponseWriter, req *http.Request) {
|
|
path := cleanPath(req.URL.Path)
|
|
switch {
|
|
case req.Method == http.MethodGet && path == "/api/admin/database/status":
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "database": r.store.Status()})
|
|
case req.Method == http.MethodPost && path == "/api/admin/database/test":
|
|
var body config.DatabaseConfig
|
|
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
|
writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err)
|
|
return
|
|
}
|
|
if body.Provider == "" {
|
|
body.Provider = r.cfg.Database.Provider
|
|
}
|
|
if body.SQLitePath == "" {
|
|
body.SQLitePath = r.cfg.Database.SQLitePath
|
|
}
|
|
if body.MySQLDSN == "" {
|
|
body.MySQLDSN = r.cfg.Database.MySQLDSN
|
|
}
|
|
if err := db.TestDatabase(body); err != nil {
|
|
writeError(w, http.StatusBadGateway, "DATABASE_TEST_FAILED", err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
case req.Method == http.MethodPost && path == "/api/admin/database/import-sqlite":
|
|
result, err := r.store.ImportSQLiteToRemote()
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "DATABASE_IMPORT_FAILED", err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "result": result})
|
|
case req.Method == http.MethodPost && path == "/api/admin/database/sync":
|
|
result, err := r.store.SyncNow()
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "DATABASE_SYNC_FAILED", err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "result": result})
|
|
default:
|
|
http.NotFound(w, req)
|
|
}
|
|
}
|
|
|
|
func (r *router) handleAdminDashboard(w http.ResponseWriter, req *http.Request) {
|
|
path := cleanPath(req.URL.Path)
|
|
if req.Method != http.MethodGet || path != "/api/admin/dashboard/overview" {
|
|
http.NotFound(w, req)
|
|
return
|
|
}
|
|
overview, err := r.store.DashboardOverview(80)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "DASHBOARD_FAILED", err)
|
|
return
|
|
}
|
|
overview["health"] = health.Snapshot(r.cfg, r.store)
|
|
writeJSON(w, http.StatusOK, overview)
|
|
}
|
|
|
|
func (r *router) handleAdminSync(w http.ResponseWriter, req *http.Request) {
|
|
if r.syncer == nil {
|
|
writeError(w, http.StatusNotFound, "SYNC_DISABLED", errors.New("legacy sync service is not configured"))
|
|
return
|
|
}
|
|
path := cleanPath(req.URL.Path)
|
|
switch {
|
|
case req.Method == http.MethodGet && path == "/api/admin/sync/legacy/preview":
|
|
writeJSON(w, http.StatusOK, r.syncer.Preview(req.Context()))
|
|
case req.Method == http.MethodPost && path == "/api/admin/sync/legacy/run":
|
|
writeJSON(w, http.StatusOK, r.syncer.Run(req.Context()))
|
|
default:
|
|
http.NotFound(w, req)
|
|
}
|
|
}
|
|
|
|
func (r *router) handleAdminEndpoints(w http.ResponseWriter, req *http.Request) {
|
|
if req.Method != http.MethodGet {
|
|
http.NotFound(w, req)
|
|
return
|
|
}
|
|
items, err := r.sources.Endpoints(true)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "ENDPOINTS_FAILED", err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": items})
|
|
}
|
|
|
|
func (r *router) handleAdminEvents(w http.ResponseWriter, req *http.Request) {
|
|
if req.Method != http.MethodGet {
|
|
writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("GET required"))
|
|
return
|
|
}
|
|
flusher, ok := w.(http.Flusher)
|
|
if !ok {
|
|
writeError(w, http.StatusInternalServerError, "SSE_UNSUPPORTED", errors.New("streaming is not supported"))
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
|
|
w.Header().Set("Cache-Control", "no-cache")
|
|
w.Header().Set("Connection", "keep-alive")
|
|
events, unsubscribe := r.sources.SubscribeEvents()
|
|
defer unsubscribe()
|
|
ticker := time.NewTicker(15 * time.Second)
|
|
defer ticker.Stop()
|
|
writeSSE(w, "ready", map[string]any{"ok": true, "time": time.Now().UTC().Format(time.RFC3339)})
|
|
flusher.Flush()
|
|
for {
|
|
select {
|
|
case event, ok := <-events:
|
|
if !ok {
|
|
return
|
|
}
|
|
writeSSE(w, event.Type, event.Data)
|
|
flusher.Flush()
|
|
case <-ticker.C:
|
|
writeSSE(w, "heartbeat", map[string]any{"time": time.Now().UTC().Format(time.RFC3339)})
|
|
flusher.Flush()
|
|
case <-req.Context().Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *router) handleAdminSystem(w http.ResponseWriter, req *http.Request) {
|
|
path := cleanPath(req.URL.Path)
|
|
switch path {
|
|
case "/api/admin/system/health":
|
|
writeJSON(w, http.StatusOK, health.Snapshot(r.cfg, r.store))
|
|
case "/api/admin/system/audit":
|
|
items, err := r.store.ListAuditLogs(100)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "AUDIT_FAILED", err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": items})
|
|
case "/api/admin/system/database/sync":
|
|
if req.Method != http.MethodPost {
|
|
writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("POST required"))
|
|
return
|
|
}
|
|
result, err := r.store.ImportSQLiteToRemote()
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "SYNC_FAILED", err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "result": result, "finishedAt": result.FinishedAt})
|
|
default:
|
|
http.NotFound(w, req)
|
|
}
|
|
}
|