generated from CubeCraft-Creations/Tracehound
fix(dashboard): keep SSE alive + seed camera list via REST
The dashboard showed "No Cameras Connected" despite the API returning the camera: - middleware.Timeout + http.Server.WriteTimeout (10s) cancelled the long-lived /api/v1/events/stream every 10s, before any 30s status event could arrive — so the SSE-fed store never populated. Drop the global request timeout and set WriteTimeout=0 (closed-LAN kiosk). - The SPA never seeded from GET /api/v1/cameras (SSE only pushes on change). Fetch the list once on mount and setCameras(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+9
-6
@@ -78,7 +78,9 @@ func main() {
|
|||||||
r.Use(middleware.RealIP)
|
r.Use(middleware.RealIP)
|
||||||
r.Use(middleware.Logger)
|
r.Use(middleware.Logger)
|
||||||
r.Use(middleware.Recoverer)
|
r.Use(middleware.Recoverer)
|
||||||
r.Use(middleware.Timeout(cfg.WriteTimeout))
|
// No global request timeout: it cancels the long-lived SSE stream
|
||||||
|
// (/api/v1/events/stream) — that's why the dashboard never received
|
||||||
|
// camera events. Closed-LAN kiosk, so dropping it is fine.
|
||||||
|
|
||||||
// Health check (no auth)
|
// Health check (no auth)
|
||||||
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -94,11 +96,12 @@ func main() {
|
|||||||
|
|
||||||
// Create server
|
// Create server
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Addr: ":" + cfg.Port,
|
Addr: ":" + cfg.Port,
|
||||||
Handler: r,
|
Handler: r,
|
||||||
ReadTimeout: cfg.ReadTimeout,
|
ReadTimeout: cfg.ReadTimeout,
|
||||||
WriteTimeout: cfg.WriteTimeout,
|
// WriteTimeout intentionally 0: SSE responses are long-lived and a
|
||||||
IdleTimeout: cfg.IdleTimeout,
|
// write deadline would terminate them mid-stream.
|
||||||
|
IdleTimeout: cfg.IdleTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
|
|||||||
+9
-1
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback, useMemo } from 'react'
|
import { useState, useCallback, useMemo, useEffect } from 'react'
|
||||||
import { Camera, Play, Square, Wifi, WifiOff, AlertTriangle } from 'lucide-react'
|
import { Camera, Play, Square, Wifi, WifiOff, AlertTriangle } from 'lucide-react'
|
||||||
import { useSSE } from './hooks/useSSE'
|
import { useSSE } from './hooks/useSSE'
|
||||||
import { useCameraStore } from './store/useCameraStore'
|
import { useCameraStore } from './store/useCameraStore'
|
||||||
@@ -15,6 +15,14 @@ function App() {
|
|||||||
// SSE connection + live store
|
// SSE connection + live store
|
||||||
const { connectionState } = useSSE()
|
const { connectionState } = useSSE()
|
||||||
|
|
||||||
|
// Seed the list once on mount via the REST API. SSE only pushes on change,
|
||||||
|
// so without this the dashboard is empty until the next status event.
|
||||||
|
useEffect(() => {
|
||||||
|
api.getCameras()
|
||||||
|
.then((list) => useCameraStore.getState().setCameras(list))
|
||||||
|
.catch(() => { /* SSE will fill in shortly */ })
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Subscribe to full camera state — dashboard needs every change
|
// Subscribe to full camera state — dashboard needs every change
|
||||||
const camerasMap = useCameraStore((s) => s.cameras)
|
const camerasMap = useCameraStore((s) => s.cameras)
|
||||||
const cameras = useMemo(() => Array.from(camerasMap.values()), [camerasMap])
|
const cameras = useMemo(() => Array.from(camerasMap.values()), [camerasMap])
|
||||||
|
|||||||
Reference in New Issue
Block a user