Add read-only mode support and enhance logging throughout the application
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2026-02-19 12:36:52 +11:00
parent bdcb8e6f73
commit 1c15ff5911
8 changed files with 281 additions and 33 deletions

View File

@@ -85,6 +85,7 @@ func NewWebGui(source mk2driver.Mk2, writer mk2driver.SettingsWriter) *WebGui {
Mode: modeUnknown,
},
}
log.WithField("remote_writable", writer != nil).Info("Web UI initialized")
w.wg.Add(1)
go w.dataPoll()
return w
@@ -138,50 +139,77 @@ type setRemotePanelStandbyRequest struct {
}
func (w *WebGui) ServeHub(rw http.ResponseWriter, r *http.Request) {
log.WithFields(logrus.Fields{
"remote": r.RemoteAddr,
"path": r.URL.Path,
}).Debug("WebSocket hub request")
w.hub.ServeHTTP(rw, r)
}
func (w *WebGui) ServeRemotePanelState(rw http.ResponseWriter, r *http.Request) {
log.WithFields(logrus.Fields{
"method": r.Method,
"path": r.URL.Path,
"remote": r.RemoteAddr,
}).Debug("Remote panel state API request")
switch r.Method {
case http.MethodGet:
writeJSON(rw, http.StatusOK, w.getRemotePanelState())
case http.MethodPost:
w.handleSetRemotePanelState(rw, r)
default:
log.WithField("method", r.Method).Warn("Remote panel state API received unsupported method")
http.Error(rw, "method not allowed", http.StatusMethodNotAllowed)
}
}
func (w *WebGui) ServeRemotePanelStandby(rw http.ResponseWriter, r *http.Request) {
log.WithFields(logrus.Fields{
"method": r.Method,
"path": r.URL.Path,
"remote": r.RemoteAddr,
}).Debug("Remote panel standby API request")
switch r.Method {
case http.MethodGet:
writeJSON(rw, http.StatusOK, w.getRemotePanelState())
case http.MethodPost:
w.handleSetRemotePanelStandby(rw, r)
default:
log.WithField("method", r.Method).Warn("Remote panel standby API received unsupported method")
http.Error(rw, "method not allowed", http.StatusMethodNotAllowed)
}
}
func (w *WebGui) handleSetRemotePanelState(rw http.ResponseWriter, r *http.Request) {
if w.writer == nil {
log.Warn("Remote panel state write requested, but writer is unavailable")
http.Error(rw, "remote control is not supported by this data source", http.StatusNotImplemented)
return
}
req := setRemotePanelStateRequest{}
if err := decodeJSONBody(r, &req); err != nil {
log.WithError(err).Warn("Invalid remote panel state request body")
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
logEntry := log.WithField("mode", req.Mode)
if req.CurrentLimit != nil {
logEntry = logEntry.WithField("current_limit_a", *req.CurrentLimit)
}
logEntry.Info("Applying remote panel state from API")
switchState, normalizedMode, err := parsePanelMode(req.Mode)
if err != nil {
logEntry.WithError(err).Warn("Unsupported remote panel mode")
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
if err := w.writer.SetPanelState(switchState, req.CurrentLimit); err != nil {
logEntry.WithError(err).Error("Failed to apply remote panel state")
w.updateRemotePanelState(func(state *remotePanelState) {
state.LastCommand = "set_remote_panel_state"
state.LastError = err.Error()
@@ -196,22 +224,27 @@ func (w *WebGui) handleSetRemotePanelState(rw http.ResponseWriter, r *http.Reque
state.LastCommand = "set_remote_panel_state"
state.LastError = ""
})
logEntry.WithField("normalized_mode", normalizedMode).Info("Remote panel state applied")
writeJSON(rw, http.StatusOK, w.getRemotePanelState())
}
func (w *WebGui) handleSetRemotePanelStandby(rw http.ResponseWriter, r *http.Request) {
if w.writer == nil {
log.Warn("Remote panel standby write requested, but writer is unavailable")
http.Error(rw, "remote control is not supported by this data source", http.StatusNotImplemented)
return
}
req := setRemotePanelStandbyRequest{}
if err := decodeJSONBody(r, &req); err != nil {
log.WithError(err).Warn("Invalid remote panel standby request body")
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
log.WithField("standby", req.Standby).Info("Applying standby state from API")
if err := w.writer.SetStandby(req.Standby); err != nil {
log.WithError(err).WithField("standby", req.Standby).Error("Failed to apply standby state")
w.updateRemotePanelState(func(state *remotePanelState) {
state.LastCommand = "set_remote_panel_standby"
state.LastError = err.Error()
@@ -225,6 +258,7 @@ func (w *WebGui) handleSetRemotePanelStandby(rw http.ResponseWriter, r *http.Req
state.LastCommand = "set_remote_panel_standby"
state.LastError = ""
})
log.WithField("standby", req.Standby).Info("Standby state applied")
writeJSON(rw, http.StatusOK, w.getRemotePanelState())
}
@@ -301,23 +335,32 @@ func buildTemplateInput(status *mk2driver.Mk2Info) *templateInput {
}
func (w *WebGui) Stop() {
log.Info("Stopping Web UI polling")
close(w.stopChan)
w.wg.Wait()
log.Info("Web UI polling stopped")
}
func (w *WebGui) dataPoll() {
for {
select {
case s := <-w.C():
if s.Valid {
payload := buildTemplateInput(s)
w.stateMu.Lock()
payload.RemotePanel = w.remote
w.latest = payload
w.stateMu.Unlock()
if err := w.hub.Broadcast(payload); err != nil {
log.Errorf("Could not send update to clients: %v", err)
}
if s == nil {
log.Debug("Skipping nil MK2 update in Web UI poller")
continue
}
if !s.Valid {
log.WithField("errors", len(s.Errors)).Debug("Skipping invalid MK2 update in Web UI poller")
continue
}
payload := buildTemplateInput(s)
w.stateMu.Lock()
payload.RemotePanel = w.remote
w.latest = payload
w.stateMu.Unlock()
if err := w.hub.Broadcast(payload); err != nil {
log.Errorf("Could not send update to clients: %v", err)
}
case <-w.stopChan:
w.wg.Done()
@@ -336,6 +379,13 @@ func (w *WebGui) updateRemotePanelState(update func(state *remotePanelState)) {
w.stateMu.Lock()
update(&w.remote)
w.remote.LastUpdated = time.Now().UTC().Format(time.RFC3339)
log.WithFields(logrus.Fields{
"mode": w.remote.Mode,
"has_limit": w.remote.CurrentLimit != nil,
"has_standby": w.remote.Standby != nil,
"last_command": w.remote.LastCommand,
"last_error": w.remote.LastError,
}).Debug("Updated remote panel state cache")
snapshot := w.snapshotLocked()
w.stateMu.Unlock()