feat: camera config proxy — access cameras remotely through NextNVR
This commit is contained in:
parent
8328f4049a
commit
efcc10f5fd
3 changed files with 28 additions and 1 deletions
25
api.go
25
api.go
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -159,7 +161,28 @@ func (s *Server) handleConfigReload(w http.ResponseWriter, r *http.Request) {
|
||||||
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: "reloaded"})
|
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: "reloaded"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleStream proxies MJPEG streams from go2rtc to avoid cross-origin issues.
|
// handleCameraProxy forwards requests to a camera's web interface.
|
||||||
|
// GET /camera/{cam_id}/... → http://{camera_ip}/...
|
||||||
|
func (s *Server) handleCameraProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := strings.TrimPrefix(r.URL.Path, "/camera/")
|
||||||
|
parts := strings.SplitN(path, "/", 2)
|
||||||
|
camID := parts[0]
|
||||||
|
|
||||||
|
cam, _ := s.findCamera(camID)
|
||||||
|
if cam == nil {
|
||||||
|
http.Error(w, "camera not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining := "/"
|
||||||
|
if len(parts) > 1 {
|
||||||
|
remaining += parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
target, _ := url.Parse("http://" + cam.IP + remaining)
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
// GET /stream/{cam_id}?type=sub (default: sub stream via go2rtc)
|
// GET /stream/{cam_id}?type=sub (default: sub stream via go2rtc)
|
||||||
func (s *Server) handleStream(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleStream(w http.ResponseWriter, r *http.Request) {
|
||||||
camID := strings.TrimPrefix(r.URL.Path, "/stream/")
|
camID := strings.TrimPrefix(r.URL.Path, "/stream/")
|
||||||
|
|
|
||||||
|
|
@ -291,6 +291,7 @@ async function renderCameraCards() {
|
||||||
<div class="camera-card" data-id="${c.id}">
|
<div class="camera-card" data-id="${c.id}">
|
||||||
<h3>📷 ${c.name || 'Camera_' + c.ip.split('.').pop()}
|
<h3>📷 ${c.name || 'Camera_' + c.ip.split('.').pop()}
|
||||||
<span style="font-size:11px;color:var(--text-muted)">${c.ip}</span>
|
<span style="font-size:11px;color:var(--text-muted)">${c.ip}</span>
|
||||||
|
<a href="/camera/${c.id}/" target="_blank" title="Open camera configuration" style="font-size:12px;color:var(--accent);margin-left:6px;text-decoration:none">⚙️</a>
|
||||||
</h3>
|
</h3>
|
||||||
<input type="hidden" value="${escAttr(c.id)}" data-field="id">
|
<input type="hidden" value="${escAttr(c.id)}" data-field="id">
|
||||||
<input type="hidden" value="${escAttr(c.ip)}" data-field="ip">
|
<input type="hidden" value="${escAttr(c.ip)}" data-field="ip">
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,9 @@ func (s *Server) registerRoutes() {
|
||||||
s.mux.Handle("/go2rtc/", http.StripPrefix("/go2rtc",
|
s.mux.Handle("/go2rtc/", http.StripPrefix("/go2rtc",
|
||||||
s.go2rtcProxy()))
|
s.go2rtcProxy()))
|
||||||
|
|
||||||
|
// Camera web interface proxy — access cameras remotely through NextNVR.
|
||||||
|
s.mux.HandleFunc("/camera/", s.handleCameraProxy)
|
||||||
|
|
||||||
// Static file server for recordings (actual video files on disk).
|
// Static file server for recordings (actual video files on disk).
|
||||||
s.mux.Handle("/recordings/", http.StripPrefix("/recordings/",
|
s.mux.Handle("/recordings/", http.StripPrefix("/recordings/",
|
||||||
http.FileServer(http.Dir(s.config.Storage.RecordingsPath))))
|
http.FileServer(http.Dir(s.config.Storage.RecordingsPath))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue