Files
av1dae/internal/server/server.go
T
Esa Kataja 8fc00c96b9 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
2026-06-21 19:51:35 +03:00

110 lines
3.0 KiB
Go

// Package server exposes a read-only HTTP status endpoint for the daemon:
// the live encode progress, the pending input queue, and recent log events.
package server
import (
_ "embed"
"encoding/json"
"net/http"
"path/filepath"
"av1dae/internal/logger"
"av1dae/internal/settings"
"av1dae/internal/status"
"av1dae/internal/watcher"
)
//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, 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
// dashboard) to this same mux.
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)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write(indexHTML)
}
type statusResponse struct {
Current status.Snapshot `json:"current"`
Queue []string `json:"queue"`
Recent []logger.LogEntry `json:"recent"`
}
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
queue := []string{}
for _, f := range watcher.InputFiles(s.inputDir) {
queue = append(queue, filepath.Base(f))
}
recent, err := s.log.RecentLogs(50)
if err != nil {
http.Error(w, "reading logs", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(statusResponse{
Current: s.tracker.Snapshot(),
Queue: queue,
Recent: recent,
})
}