mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 08:54:55 +08:00
feat(bat): 完善工作流调度与 dashboard RPC
补全资源拉取、解析、翻译、重打包和本地化发布命令,支持单次、限定次数与周期调度。移除 TUI 计划并通过 schedule.* RPC 暴露给 bat-api dashboard。 Closes #43
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
)
|
||||
|
||||
const adminControlMaxBodyBytes = 1024
|
||||
const adminScheduleMaxBodyBytes = 64 * 1024
|
||||
|
||||
type adminControlRequest struct {
|
||||
Force bool `json:"force"`
|
||||
@@ -33,6 +34,7 @@ func (s *Server) handleAdminIndex(w http.ResponseWriter, r *http.Request) {
|
||||
"/v1/release",
|
||||
"/v1/resources",
|
||||
"/openapi.yaml",
|
||||
"/admin/schedules",
|
||||
},
|
||||
Controls: []string{
|
||||
"/admin/control/reload",
|
||||
@@ -42,6 +44,10 @@ func (s *Server) handleAdminIndex(w http.ResponseWriter, r *http.Request) {
|
||||
"/admin/control/verify",
|
||||
"/admin/control/repair",
|
||||
"/admin/control/catalog-refresh",
|
||||
"/admin/control/schedule-add",
|
||||
"/admin/control/schedule-update",
|
||||
"/admin/control/schedule-remove",
|
||||
"/admin/control/schedule-run",
|
||||
},
|
||||
}
|
||||
if r.Method == http.MethodHead {
|
||||
@@ -65,6 +71,10 @@ func (s *Server) handleAdminControl(w http.ResponseWriter, r *http.Request) {
|
||||
writeErrorJSON(w, http.StatusNotFound, "control_not_found", "unknown control action")
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(action, "schedule-") {
|
||||
s.handleAdminScheduleControl(w, r, action)
|
||||
return
|
||||
}
|
||||
request, ok := decodeAdminControlRequest(w, r)
|
||||
if !ok {
|
||||
return
|
||||
@@ -138,6 +148,85 @@ func (s *Server) handleAdminControl(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminSchedules(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
writeErrorJSON(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
|
||||
return
|
||||
}
|
||||
if !s.requireAdminToken(w, r) {
|
||||
return
|
||||
}
|
||||
backend, ok := s.backend.(ScheduleBackend)
|
||||
if !ok || backend == nil {
|
||||
writeErrorJSON(w, http.StatusServiceUnavailable, "schedule_backend_unavailable", "Rust bat schedule backend is unavailable")
|
||||
return
|
||||
}
|
||||
result, err := backend.ScheduleList(r.Context())
|
||||
if err != nil {
|
||||
s.writeControlBackendError(w, "schedule-list", err)
|
||||
return
|
||||
}
|
||||
if r.Method == http.MethodHead {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
writeNoStoreJSON(w, http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminScheduleControl(w http.ResponseWriter, r *http.Request, action string) {
|
||||
backend, ok := s.backend.(ScheduleBackend)
|
||||
if !ok || backend == nil {
|
||||
writeErrorJSON(w, http.StatusServiceUnavailable, "schedule_backend_unavailable", "Rust bat schedule backend is unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
method string
|
||||
result json.RawMessage
|
||||
err error
|
||||
)
|
||||
switch action {
|
||||
case "schedule-add", "schedule-update", "schedule-remove":
|
||||
var params backendrpc.ScheduleMutationParams
|
||||
if !decodeAdminScheduleJSON(w, r, ¶ms) {
|
||||
return
|
||||
}
|
||||
switch action {
|
||||
case "schedule-add":
|
||||
method = "schedule.add"
|
||||
result, err = backend.ScheduleAdd(r.Context(), params)
|
||||
case "schedule-update":
|
||||
method = "schedule.update"
|
||||
result, err = backend.ScheduleUpdate(r.Context(), params)
|
||||
default:
|
||||
method = "schedule.remove"
|
||||
result, err = backend.ScheduleRemove(r.Context(), params)
|
||||
}
|
||||
case "schedule-run":
|
||||
var params backendrpc.ScheduleRunParams
|
||||
if !decodeAdminScheduleJSON(w, r, ¶ms) {
|
||||
return
|
||||
}
|
||||
method = "schedule.run"
|
||||
result, err = backend.ScheduleRun(r.Context(), params)
|
||||
default:
|
||||
writeErrorJSON(w, http.StatusNotFound, "control_not_found", "unknown control action")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
s.writeControlBackendError(w, action, err)
|
||||
return
|
||||
}
|
||||
writeNoStoreJSON(w, http.StatusAccepted, AdminControlResponse{
|
||||
Service: "bat-api",
|
||||
Action: action,
|
||||
RPCMethod: method,
|
||||
Status: "accepted",
|
||||
Result: result,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) requireAdminToken(w http.ResponseWriter, r *http.Request) bool {
|
||||
if s.cfg.AuthToken == "" {
|
||||
writeErrorJSON(w, http.StatusForbidden, "admin_auth_required", "admin controls require BAT_API_AUTH_TOKEN")
|
||||
@@ -151,6 +240,26 @@ func (s *Server) requireAdminToken(w http.ResponseWriter, r *http.Request) bool
|
||||
return true
|
||||
}
|
||||
|
||||
func decodeAdminScheduleJSON(w http.ResponseWriter, r *http.Request, target any) bool {
|
||||
if r.Body == nil {
|
||||
return true
|
||||
}
|
||||
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, adminScheduleMaxBodyBytes))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(target); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return true
|
||||
}
|
||||
writeErrorJSON(w, http.StatusBadRequest, "invalid_schedule_params", "schedule request must be a JSON object")
|
||||
return false
|
||||
}
|
||||
if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
|
||||
writeErrorJSON(w, http.StatusBadRequest, "invalid_schedule_params", "schedule request must contain exactly one JSON object")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func decodeAdminControlRequest(w http.ResponseWriter, r *http.Request) (adminControlRequest, bool) {
|
||||
var request adminControlRequest
|
||||
if r.Body == nil {
|
||||
|
||||
Reference in New Issue
Block a user