服务端媒体源导入/保存/客户端输出链路修复:支持 snake/camel、subcategories/sources,默认客户端可见,保存后发布兼容 media-types.json。
build-winui / winui (push) Waiting to run

新增数据库同步 Job API、持久化状态、实时输出、最新任务恢复,以及系统日志聚合接口。
管理端优化:日志中心、运维实时状态框、同步输出自动滚动、仪表盘“输出”列、真实延迟空态、本地 favicon/avatar。
新增 server/unified-management/assets/favicon.ico 和 developer-avatar.png,并接好 /favicon.ico、/admin/favicon.ico、/setup/favicon.ico、/assets/*。
WinUI 随机放映室卡片优先显示子接口原始 Description。
Inno 安装器输出框改为选区末尾 + SendMessage 滚动到底部。
This commit is contained in:
QWQLwToo
2026-06-29 22:28:58 +08:00
parent f00124c1c0
commit 7745e7a2d4
36 changed files with 1482 additions and 153 deletions
@@ -6,6 +6,7 @@ import (
"net/http"
"path/filepath"
"strconv"
"strings"
"time"
"ymhut-box/server/unified-management/internal/config"
@@ -55,6 +56,40 @@ func (r *router) handleAdminDatabase(w http.ResponseWriter, req *http.Request) {
}
_ = r.store.InsertAudit(db.AuditLog{Actor: "admin", Type: "system.database.saved", Target: body.Provider, Message: "数据库配置已保存并热切换", IP: req.RemoteAddr, UserAgent: req.UserAgent()})
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "database": r.store.Status(), "config": config.SafeDatabase(r.cfg.BaseDir, r.cfg.Database)})
case req.Method == http.MethodPost && path == "/api/admin/database/sync/jobs":
var body struct {
Direction string `json:"direction"`
}
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
writeError(w, http.StatusBadRequest, "INVALID_PAYLOAD", err)
return
}
job, err := r.store.QueueDatabaseSync(body.Direction)
if err != nil {
writeError(w, http.StatusConflict, "DATABASE_SYNC_RUNNING", err)
return
}
_ = r.store.InsertAudit(db.AuditLog{Actor: "admin", Type: "database.sync.queued", Target: job.Direction, Message: "数据库同步任务已启动", IP: req.RemoteAddr, UserAgent: req.UserAgent()})
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "jobId": job.ID, "job": job})
case req.Method == http.MethodGet && path == "/api/admin/database/sync/jobs/latest":
job, err := r.store.LatestDatabaseSyncJob()
if err != nil {
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "job": nil})
return
}
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "job": job})
case req.Method == http.MethodGet && strings.HasPrefix(path, "/api/admin/database/sync/jobs/"):
id, err := strconv.ParseInt(strings.TrimPrefix(path, "/api/admin/database/sync/jobs/"), 10, 64)
if err != nil || id <= 0 {
writeError(w, http.StatusBadRequest, "INVALID_JOB_ID", errors.New("invalid sync job id"))
return
}
job, err := r.store.GetDatabaseSyncJob(id)
if err != nil {
writeError(w, http.StatusNotFound, "SYNC_JOB_NOT_FOUND", err)
return
}
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "job": job})
case req.Method == http.MethodPost && path == "/api/admin/database/import-sqlite":
result, err := r.store.ImportSQLiteToRemote()
if err != nil {
@@ -218,6 +253,22 @@ func (r *router) handleAdminSystem(w http.ResponseWriter, req *http.Request) {
return
}
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": page.Items, "page": page})
case "/api/admin/system/logs":
if req.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("GET required"))
return
}
page, err := r.store.ListSystemLogsPage(db.SystemLogFilters{
Page: queryInt(req, "page", 1),
PerPage: queryInt(req, "perPage", 35),
Category: req.URL.Query().Get("category"),
Query: req.URL.Query().Get("q"),
})
if err != nil {
writeError(w, http.StatusInternalServerError, "SYSTEM_LOGS_FAILED", err)
return
}
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": page.Items, "page": page})
case "/api/admin/system/mail/config":
r.handleMailConfig(w, req)
case "/api/admin/system/mail/test":