Update application UI and functionality

This commit is contained in:
2026-07-26 16:20:36 +08:00
parent b9aff58f32
commit 97ea6fb7aa
48 changed files with 2790 additions and 628 deletions
@@ -1,6 +1,7 @@
package web
import (
"context"
"encoding/json"
"errors"
"net/http"
@@ -31,6 +32,8 @@ type router struct {
publicSnapshots *publicSnapshotService
}
const loginRequestTimeout = 5 * time.Second
func NewRouter(cfg *config.Config, store *db.Store, authService *auth.Service, feedbackService *feedback.Service, releaseService *releases.Service, sourceService *sources.Service, legacyService *legacy.Service, optional ...any) http.Handler {
r := &router{
cfg: cfg,
@@ -55,7 +58,10 @@ func NewRouter(cfg *config.Config, store *db.Store, authService *auth.Service, f
func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := cleanPath(req.URL.Path)
if strings.HasPrefix(path, "/api/admin/") && req.Method != http.MethodGet && req.Method != http.MethodHead {
if strings.HasPrefix(path, "/api/admin/") &&
path != "/api/admin/auth/login" &&
path != "/api/admin/auth/logout" &&
req.Method != http.MethodGet && req.Method != http.MethodHead {
captured := &mutationResponseWriter{ResponseWriter: w, status: http.StatusOK}
w = captured
defer func() {
@@ -116,7 +122,7 @@ func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case strings.HasPrefix(path, "/downloads/"):
r.handleDownload(w, req)
case strings.HasPrefix(path, "/admin/assets/"):
serveStaticAsset(w, req, r.cfg.AdminWebDir, "admin/dist", strings.TrimPrefix(path, "/admin/"))
r.serveAdminAsset(w, req, strings.TrimPrefix(path, "/admin/"))
case strings.HasPrefix(path, "/assets/"):
serveStaticAsset(w, req, r.cfg.PortalWebDir, "portal/dist", strings.TrimPrefix(path, "/"))
case strings.HasPrefix(path, "/api/admin/feedbacks"):
@@ -196,6 +202,8 @@ func (r *router) handleLogin(w http.ResponseWriter, req *http.Request) {
writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("POST required"))
return
}
w.Header().Set("Cache-Control", "no-store")
req.Body = http.MaxBytesReader(w, req.Body, 64<<10)
var body struct {
Username string `json:"username"`
Password string `json:"password"`
@@ -209,8 +217,14 @@ func (r *router) handleLogin(w http.ResponseWriter, req *http.Request) {
if body.Username == "" {
body.Username = "admin"
}
sessionID, csrf, ok, err := r.auth.Login(req.Context(), body.Username, body.Password, body.CaptchaID, body.Captcha, req.RemoteAddr)
ctx, cancel := context.WithTimeout(req.Context(), loginRequestTimeout)
defer cancel()
sessionID, csrf, ok, err := r.auth.Login(ctx, body.Username, body.Password, body.CaptchaID, body.Captcha, req.RemoteAddr)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
writeError(w, http.StatusGatewayTimeout, "LOGIN_TIMEOUT", errors.New("login verification timed out"))
return
}
writeError(w, http.StatusInternalServerError, "LOGIN_FAILED", err)
return
}
@@ -219,8 +233,16 @@ func (r *router) handleLogin(w http.ResponseWriter, req *http.Request) {
return
}
auth.SetSessionCookieForRequest(w, req, sessionID)
_ = r.store.InsertAudit(db.AuditLog{Actor: body.Username, Type: "auth.login", Target: "admin", Message: "管理员登录", IP: req.RemoteAddr, UserAgent: req.UserAgent()})
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "csrfToken": csrf, "user": map[string]any{"username": body.Username}})
go r.recordLoginAudit(body.Username, req.RemoteAddr, req.UserAgent())
}
func (r *router) recordLoginAudit(username, remoteAddr, userAgent string) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_ = r.store.InsertAuditContext(ctx, db.AuditLog{
Actor: username, Type: "auth.login", Target: "admin", Message: "管理员登录", IP: remoteAddr, UserAgent: userAgent,
})
}
func (r *router) handleLogout(w http.ResponseWriter, req *http.Request) {