diff --git a/api.go b/api.go index 4922c03..fc8996a 100644 --- a/api.go +++ b/api.go @@ -138,6 +138,19 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) { } } +// handleConfigReload restarts internal services with the current config. +// POST /api/config/reload +func (s *Server) handleConfigReload(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Error: "method not allowed"}) + return + } + if s.onReload != nil { + s.onReload() + } + jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: "reloaded"}) +} + // handleStream proxies MJPEG streams from go2rtc to avoid cross-origin issues. // GET /stream/{cam_id}?type=sub (default: sub stream via go2rtc) func (s *Server) handleStream(w http.ResponseWriter, r *http.Request) { diff --git a/main.go b/main.go index 4a5d514..32a1a96 100644 --- a/main.go +++ b/main.go @@ -77,6 +77,25 @@ func main() { // Start the HTTP server (main port, with auth). app.server, _ = NewServer(cfg) + app.server.onReload = func() { + log.Println("Config reload — restarting services...") + if app.go2rtc != nil { app.go2rtc.Stop() } + if app.snapshots != nil { app.snapshots.StopAll() } + if app.recorder != nil { app.recorder.StopAll() } + // Restart with the updated config from the server's in-memory copy. + liveCfg := app.server.appConfig + if liveCfg.Go2RTC.Enabled { + app.go2rtc = NewGo2RTCManager(liveCfg.Go2RTC) + app.go2rtc.Start(liveCfg.Cameras) + } + snaps := NewSnapshotEngine(liveCfg.Storage) + snaps.StartAll(liveCfg.Cameras) + app.snapshots = snaps + rec := NewRecorderManager(liveCfg.Storage) + rec.StartAll(liveCfg.Cameras) + app.recorder = rec + log.Println("Config reload — services restarted.") + } errCh := make(chan error, 1) go func() { errCh <- app.server.ListenAndServe() diff --git a/public/app.js b/public/app.js index 201654f..6b58374 100644 --- a/public/app.js +++ b/public/app.js @@ -378,14 +378,16 @@ document.getElementById('btn-save').addEventListener('click', async () => { }); const j = await r.json(); if (j.success) { - // Reload cameras from the server (in-memory config is now updated). + // Reload cameras from the server. await loadCameras(); renderLiveGrid(); renderPlaybackCameras(); renderCameraCards(); - status.textContent = '✅ Cameras saved! Switching to Live Wall...'; + status.textContent = '✅ Saved! Restarting services...'; status.style.color = 'var(--green)'; - setTimeout(() => switchTab('live'), 800); + // Trigger backend reload of cameras. + try { await fetch(API + '/config/reload', { method: 'POST' }); } catch(e) {} + setTimeout(() => switchTab('live'), 1500); } else { status.textContent = '❌ Save failed: ' + j.error; status.style.color = 'var(--red)'; diff --git a/server.go b/server.go index 404a2da..1640c1c 100644 --- a/server.go +++ b/server.go @@ -28,6 +28,7 @@ type Server struct { appConfig *Config mu sync.RWMutex sessions *SessionStore + onReload func() // called when config is saved via API } // NewServer creates and configures the HTTP server. @@ -116,6 +117,7 @@ func (s *Server) registerRoutes() { s.mux.HandleFunc("/api/cameras", s.handleCameras) s.mux.HandleFunc("/api/cameras/", s.handleCameraByID) s.mux.HandleFunc("/api/config", s.handleConfig) + s.mux.HandleFunc("/api/config/reload", s.handleConfigReload) s.mux.HandleFunc("/api/scan", s.handleScan) s.mux.HandleFunc("/api/status", s.handleStatus) s.mux.HandleFunc("/api/recordings", s.handleRecordings)