add: DB-backed settings UI for live-editable tunables
Add a settings page (/settings) and API (/api/settings GET/PUT) to edit encoding profiles (crf/preset per media type), lp, OMDb key, log retention, and delete-originals from the browser, persisted to a settings table in logs.db. config.yaml seeds the store on first run; afterwards the DB is the source of truth (paths.* and http_addr stay config-only). Each job snapshots the current settings, so changes apply to the next encode with no restart. PUT validates ranges (crf 0-63, preset 0-13, lp >=0, retention >=1) before persisting. No auth, by design for now (isolated network) — see #2. Closes #1
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"av1dae/internal/logger"
|
||||
"av1dae/internal/settings"
|
||||
"av1dae/internal/status"
|
||||
"av1dae/internal/watcher"
|
||||
)
|
||||
@@ -16,14 +17,18 @@ import (
|
||||
//go:embed index.html
|
||||
var indexHTML []byte
|
||||
|
||||
//go:embed settings.html
|
||||
var settingsHTML []byte
|
||||
|
||||
type Server struct {
|
||||
tracker *status.Tracker
|
||||
log *logger.Logger
|
||||
store *settings.Store
|
||||
inputDir string
|
||||
}
|
||||
|
||||
func New(tracker *status.Tracker, log *logger.Logger, inputDir string) *Server {
|
||||
return &Server{tracker: tracker, log: log, inputDir: inputDir}
|
||||
func New(tracker *status.Tracker, log *logger.Logger, store *settings.Store, inputDir string) *Server {
|
||||
return &Server{tracker: tracker, log: log, store: store, inputDir: inputDir}
|
||||
}
|
||||
|
||||
// Handler returns the mux for all status routes. Phase 3 adds "/" (the HTML
|
||||
@@ -31,10 +36,43 @@ func New(tracker *status.Tracker, log *logger.Logger, inputDir string) *Server {
|
||||
func (s *Server) Handler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/status", s.handleStatus)
|
||||
mux.HandleFunc("/settings", s.handleSettingsPage)
|
||||
mux.HandleFunc("/api/settings", s.handleAPISettings)
|
||||
mux.HandleFunc("/", s.handleIndex)
|
||||
return mux
|
||||
}
|
||||
|
||||
func (s *Server) handleSettingsPage(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write(settingsHTML)
|
||||
}
|
||||
|
||||
// handleAPISettings serves the current settings (GET) and saves new ones (PUT).
|
||||
// Validation lives in settings.Store.Set; a bad payload returns 400.
|
||||
func (s *Server) handleAPISettings(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(s.store.Get())
|
||||
case http.MethodPut:
|
||||
var in settings.Settings
|
||||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||||
http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := s.store.Set(in); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s.log.SetRetention(in.LogRetentionDays)
|
||||
s.log.Info("Settings updated via web UI")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
default:
|
||||
w.Header().Set("Allow", "GET, PUT")
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
|
||||
Reference in New Issue
Block a user