feat: complete 2.0.7.12 platform overhaul
This commit is contained in:
@@ -4,17 +4,21 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ymhut-box/server/unified-management/internal/adminassets"
|
||||
"ymhut-box/server/unified-management/internal/auth"
|
||||
"ymhut-box/server/unified-management/internal/config"
|
||||
"ymhut-box/server/unified-management/internal/db"
|
||||
"ymhut-box/server/unified-management/internal/feedback"
|
||||
"ymhut-box/server/unified-management/internal/health"
|
||||
"ymhut-box/server/unified-management/internal/legacy"
|
||||
"ymhut-box/server/unified-management/internal/notices"
|
||||
"ymhut-box/server/unified-management/internal/reference"
|
||||
"ymhut-box/server/unified-management/internal/releases"
|
||||
"ymhut-box/server/unified-management/internal/sources"
|
||||
"ymhut-box/server/unified-management/internal/synclegacy"
|
||||
@@ -29,8 +33,12 @@ type router struct {
|
||||
sources *sources.Service
|
||||
legacy *legacy.Service
|
||||
notices *notices.Service
|
||||
referenceData *reference.Service
|
||||
syncer *synclegacy.Service
|
||||
publicSnapshots *publicSnapshotService
|
||||
adminAssets *adminassets.Service
|
||||
health *health.Service
|
||||
dashboardCache *dashboardSnapshotCache
|
||||
}
|
||||
|
||||
const loginRequestTimeout = 8 * time.Second
|
||||
@@ -44,7 +52,17 @@ func NewRouter(cfg *config.Config, store *db.Store, authService *auth.Service, f
|
||||
releases: releaseService,
|
||||
sources: sourceService,
|
||||
legacy: legacyService,
|
||||
referenceData: reference.NewService(cfg.UpdatePublicDir),
|
||||
publicSnapshots: newPublicSnapshotService(60 * time.Second),
|
||||
adminAssets: adminassets.New(cfg.AdminAssetMode, cfg.AdminWebDir, config.AdminBuildID),
|
||||
health: health.NewService(cfg, store),
|
||||
dashboardCache: newDashboardSnapshotCache(5 * time.Second),
|
||||
}
|
||||
assetStatus := r.adminAssets.Diagnostics()
|
||||
if assetStatus.Ready {
|
||||
log.Printf("admin assets: mode=%s build=%s manifest=%s entries=%d", assetStatus.Mode, assetStatus.BuildID, assetStatus.ManifestStatus, assetStatus.ManifestEntries)
|
||||
} else {
|
||||
log.Printf("admin assets unavailable: mode=%s source=%s error=%s", assetStatus.Mode, assetStatus.Source, assetStatus.ValidationError)
|
||||
}
|
||||
for _, item := range optional {
|
||||
switch typed := item.(type) {
|
||||
@@ -54,11 +72,32 @@ func NewRouter(cfg *config.Config, store *db.Store, authService *auth.Service, f
|
||||
r.syncer = typed
|
||||
}
|
||||
}
|
||||
if releaseService != nil {
|
||||
releaseService.SetChangeCallback(func() {
|
||||
r.publicSnapshots.Invalidate()
|
||||
r.dashboardCache.Invalidate()
|
||||
})
|
||||
}
|
||||
if sourceService != nil {
|
||||
sourceService.SetChangeCallback(r.dashboardCache.Invalidate)
|
||||
}
|
||||
return withSecurity(r)
|
||||
}
|
||||
|
||||
func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
req.RemoteAddr = clientAddress(req)
|
||||
path := cleanPath(req.URL.Path)
|
||||
if strings.HasPrefix(path, "/api/admin/") {
|
||||
w.Header().Set("X-Admin-Build-ID", r.adminAssets.Diagnostics().BuildID)
|
||||
if path != "/api/admin/events" {
|
||||
diagnostics := &diagnosticResponseWriter{ResponseWriter: w, status: http.StatusOK}
|
||||
w = diagnostics
|
||||
started := time.Now()
|
||||
defer func() {
|
||||
log.Printf("admin_api method=%s path=%s status=%d duration_ms=%d cache=%s build=%s", req.Method, path, diagnostics.status, time.Since(started).Milliseconds(), firstNonEmpty(w.Header().Get("X-Admin-Cache"), "n/a"), r.adminAssets.Diagnostics().BuildID)
|
||||
}()
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(path, "/api/admin/") &&
|
||||
path != "/api/admin/auth/login" &&
|
||||
path != "/api/admin/auth/logout" &&
|
||||
@@ -70,6 +109,7 @@ func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
r.publicSnapshots.Invalidate()
|
||||
r.dashboardCache.Invalidate()
|
||||
if r.sources != nil {
|
||||
r.sources.PublishEvent(adminMutationEvent(path), map[string]any{"path": path, "method": req.Method, "time": time.Now().UTC().Format(time.RFC3339)})
|
||||
}
|
||||
@@ -110,6 +150,8 @@ func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
r.handleClientEndpoints(w, req)
|
||||
case path == "/api/client/notices" || strings.HasPrefix(path, "/api/client/notices/"):
|
||||
r.handleClientNotices(w, req)
|
||||
case strings.HasPrefix(path, "/api/client/reference-data/"):
|
||||
r.handleClientReferenceData(w, req)
|
||||
case path == "/api/client/endpoint-calls" || path == "/api/client/source-calls":
|
||||
r.handleSourceCall(w, req)
|
||||
case path == "/update-info.json" || path == "/update-info":
|
||||
@@ -160,6 +202,16 @@ type mutationResponseWriter struct {
|
||||
status int
|
||||
}
|
||||
|
||||
type diagnosticResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (w *diagnosticResponseWriter) WriteHeader(status int) {
|
||||
w.status = status
|
||||
w.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (w *mutationResponseWriter) WriteHeader(status int) {
|
||||
w.status = status
|
||||
w.ResponseWriter.WriteHeader(status)
|
||||
@@ -181,11 +233,13 @@ func adminMutationEvent(path string) string {
|
||||
}
|
||||
|
||||
func (r *router) handleAuthBootstrap(w http.ResponseWriter, req *http.Request) {
|
||||
payload, err := r.auth.Bootstrap(req.Context())
|
||||
_, _, authenticated := r.auth.UserForRequest(req)
|
||||
payload, err := r.auth.Bootstrap(req.Context(), authenticated)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "BOOTSTRAP_FAILED", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
writeJSON(w, http.StatusOK, payload)
|
||||
}
|
||||
|
||||
@@ -264,6 +318,35 @@ func remoteHost(remoteAddress string) string {
|
||||
return strings.TrimSpace(remoteAddress)
|
||||
}
|
||||
|
||||
func clientAddress(req *http.Request) string {
|
||||
if req == nil {
|
||||
return ""
|
||||
}
|
||||
peer := remoteHost(req.RemoteAddr)
|
||||
peerIP := net.ParseIP(strings.Trim(peer, "[]"))
|
||||
if peerIP == nil || !peerIP.IsLoopback() {
|
||||
return peer
|
||||
}
|
||||
if forwarded := validClientIP(req.Header.Get("X-Real-IP")); forwarded != "" {
|
||||
return forwarded
|
||||
}
|
||||
values := strings.Split(req.Header.Get("X-Forwarded-For"), ",")
|
||||
for index := len(values) - 1; index >= 0; index-- {
|
||||
if forwarded := validClientIP(values[index]); forwarded != "" {
|
||||
return forwarded
|
||||
}
|
||||
}
|
||||
return peer
|
||||
}
|
||||
|
||||
func validClientIP(value string) string {
|
||||
value = strings.Trim(strings.TrimSpace(value), "[]")
|
||||
if parsed := net.ParseIP(value); parsed != nil {
|
||||
return parsed.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *router) recordLoginAudit(username, remoteAddr, userAgent string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
Reference in New Issue
Block a user