fix: merge general improvements from release branch
Backend: - gateway_handler: pass subject.UserID instead of int64(0) for user-level routing - setting_handler: add missing BalanceLowNotifyRechargeURL to UpdateSettings response - openai_gateway_service: use applyAccountStatsCost for account stats pricing integration - embed_on: add local file override (data/public/) for embedded frontend assets Frontend: - useTableSelection: add batchUpdate method for batch operations - AccountsView: virtual scrolling params, Set-based isSelected, swipe virtualization - ProxiesView: add batchUpdate to selection and swipe-select - BulkEditAccountModal: fix submit handler to prevent event object as argument - SettingsView: move payload construction outside try block - i18n: add general translation keys (saved, deleted, view, validation, allowUserRefund) - api/client: reorder error fields for consistency - stores/payment: clarify pollOrderStatus JSDoc
This commit is contained in:
@@ -10,6 +10,8 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -32,11 +34,12 @@ type PublicSettingsProvider interface {
|
||||
|
||||
// FrontendServer serves the embedded frontend with settings injection
|
||||
type FrontendServer struct {
|
||||
distFS fs.FS
|
||||
fileServer http.Handler
|
||||
baseHTML []byte
|
||||
cache *HTMLCache
|
||||
settings PublicSettingsProvider
|
||||
distFS fs.FS
|
||||
fileServer http.Handler
|
||||
baseHTML []byte
|
||||
cache *HTMLCache
|
||||
settings PublicSettingsProvider
|
||||
overrideDir string // local file override directory
|
||||
}
|
||||
|
||||
// NewFrontendServer creates a new frontend server with settings injection
|
||||
@@ -62,11 +65,12 @@ func NewFrontendServer(settingsProvider PublicSettingsProvider) (*FrontendServer
|
||||
cache.SetBaseHTML(baseHTML)
|
||||
|
||||
return &FrontendServer{
|
||||
distFS: distFS,
|
||||
fileServer: http.FileServer(http.FS(distFS)),
|
||||
baseHTML: baseHTML,
|
||||
cache: cache,
|
||||
settings: settingsProvider,
|
||||
distFS: distFS,
|
||||
fileServer: http.FileServer(http.FS(distFS)),
|
||||
baseHTML: baseHTML,
|
||||
cache: cache,
|
||||
settings: settingsProvider,
|
||||
overrideDir: filepath.Join("data", "public"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -99,6 +103,11 @@ func (s *FrontendServer) Middleware() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// Try local override first
|
||||
if s.tryServeOverride(c, cleanPath) {
|
||||
return
|
||||
}
|
||||
|
||||
// Serve static files normally
|
||||
s.fileServer.ServeHTTP(c.Writer, c.Request)
|
||||
c.Abort()
|
||||
@@ -114,6 +123,22 @@ func (s *FrontendServer) fileExists(path string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// tryServeOverride checks if a local override file exists and serves it.
|
||||
// Files in overrideDir take precedence over embedded files.
|
||||
func (s *FrontendServer) tryServeOverride(c *gin.Context, cleanPath string) bool {
|
||||
if s.overrideDir == "" {
|
||||
return false
|
||||
}
|
||||
filePath := filepath.Join(s.overrideDir, filepath.Clean("/"+cleanPath))
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil || info.IsDir() {
|
||||
return false
|
||||
}
|
||||
c.File(filePath)
|
||||
c.Abort()
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *FrontendServer) serveIndexHTML(c *gin.Context) {
|
||||
// Get nonce from context (generated by SecurityHeaders middleware)
|
||||
nonce := middleware.GetNonceFromContext(c)
|
||||
@@ -226,6 +251,7 @@ func ServeEmbeddedFrontend() gin.HandlerFunc {
|
||||
panic("failed to get dist subdirectory: " + err.Error())
|
||||
}
|
||||
fileServer := http.FileServer(http.FS(distFS))
|
||||
overrideDir := filepath.Join("data", "public")
|
||||
|
||||
return func(c *gin.Context) {
|
||||
path := c.Request.URL.Path
|
||||
@@ -242,6 +268,10 @@ func ServeEmbeddedFrontend() gin.HandlerFunc {
|
||||
|
||||
if file, err := distFS.Open(cleanPath); err == nil {
|
||||
_ = file.Close()
|
||||
// Try local override first
|
||||
if tryServeOverrideFile(c, overrideDir, cleanPath) {
|
||||
return
|
||||
}
|
||||
fileServer.ServeHTTP(c.Writer, c.Request)
|
||||
c.Abort()
|
||||
return
|
||||
@@ -251,6 +281,21 @@ func ServeEmbeddedFrontend() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// tryServeOverrideFile is a standalone version of tryServeOverride for legacy usage.
|
||||
func tryServeOverrideFile(c *gin.Context, overrideDir, cleanPath string) bool {
|
||||
if overrideDir == "" {
|
||||
return false
|
||||
}
|
||||
filePath := filepath.Join(overrideDir, filepath.Clean("/"+cleanPath))
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil || info.IsDir() {
|
||||
return false
|
||||
}
|
||||
c.File(filePath)
|
||||
c.Abort()
|
||||
return true
|
||||
}
|
||||
|
||||
func shouldBypassEmbeddedFrontend(path string) bool {
|
||||
trimmed := strings.TrimSpace(path)
|
||||
return strings.HasPrefix(trimmed, "/api/") ||
|
||||
|
||||
Reference in New Issue
Block a user