The P6 web platform is an embedded companion server. It exposes the existing Dashboard state to a browser without creating a second configuration database or moving camera credentials into JavaScript.
Open Settings > Web in the desktop application, enable the server and save the settings. The default endpoint is:
http://127.0.0.1:8080
The same executable can run without loading the QML window:
appOpenIPC-Dashboard --server-onlyServer-only mode uses the saved Web settings but starts the HTTP listener even when automatic startup is disabled. The initial administrator can be created in the desktop UI or once on a new headless profile with --initialize-admin and OPENIPC_INITIAL_ADMIN_PASSWORD_FILE; see Secure Web deployment.
Set OPENIPC_DATA_ROOT for a dedicated server profile. When present, core state, users, QSettings,
analytics storage, modules and logs are resolved below that directory instead of the interactive
desktop user's paths. --data-root <absolute-path> provides the same override for service launchers.
Only one Dashboard process can listen on a configured HTTP/WebSocket port pair. Do not run the desktop Web server and a separate --server-only instance on the same ports. If the settings page reports The bound address is already in use, close the previous Dashboard process or assign a different port pair, then apply and restart the server.
- The default
localhostprofile forces127.0.0.1even if an old bind address remains in settings. lan,vpnandreverse_proxyare explicit deployment profiles. Use0.0.0.0only on a protected management network when all interfaces are intentional.- Do not forward the plain HTTP port from a router to the Internet.
- For remote access, prefer WireGuard/Tailscale/another VPN or an HTTPS reverse proxy with strict network allowlists.
- A valid HTTPS reverse-proxy profile forces secure cookies and requires an exact trusted proxy IP list and external base URL.
The server does not enable CORS. Browser API access is same-origin by design.
The web server uses the desktop user database and the same permission mask:
| API area | Required permission |
|---|---|
| Dashboard, cameras and health status | Live View |
| Start health checks; discover, add, edit and delete cameras | Settings |
| Archive list and playback | Playback |
| Archive/snapshot download | Export |
| Analytics modules and events | Analytics |
| PTZ commands | PTZ |
| Desktop/Web push-to-talk | Talk (separate 0x80 permission) |
| Recording and monitor controls | Live View |
| Settings, logs, diagnostics and device administration | Settings |
| User and active-session administration | User Manage |
Session tokens contain 256 random bits. Only their SHA-256 digests are held in memory. Browser cookies are HttpOnly, SameSite=Strict and use a bounded, sliding expiration with a 24-hour absolute lifetime. Password, permission and user changes invalidate the affected user's web sessions without interrupting unrelated operators. Administrators can inspect opaque session IDs, origin/peer, idle/absolute expiry and revoke reason; raw tokens are never listed.
An operator may additionally have a camera scope containing stable camera IDs. An empty scope is
the backward-compatible all cameras value. A non-empty scope is enforced in the Qt monitor,
archive and operational views and on HTTP/WebSocket camera paths, including direct preview URLs,
WebRTC, recording, PTZ, health, analytics, archive and device operations. Hiding a camera in a
client is never treated as authorization for server requests. The scope representation is
intentionally ready for future site: and area: resolution in P12.
Global logs and diagnostic bundles are hidden and rejected for camera-scoped sessions because
their free-form content can reference cameras outside that scope.
Five failed logins from one address within a minute block that address for five minutes. Authenticated mutations are limited to 60 requests per session/address per minute. State-changing cookie requests require X-OpenIPC-CSRF: 1; requests with an Origin header must match the server host. Confirmed device mutations and Majestic apply requests additionally require an Idempotency-Key.
Public:
GET /api/v1/serverGET /api/v1/health/liveGET /api/v1/health/readyPOST /api/v1/auth/login
Authenticated:
GET /api/v1/auth/sessionPOST /api/v1/auth/logoutGET /api/v1/presentationGET /api/v1/dashboardGET /api/v1/camerasGET /api/v1/discoveryGET /api/v1/cameras/<index>/preview.mjpeg?quality=sd|hd(continuous browser preview)GET /api/v1/cameras/<index>/preview.jpg?quality=sd|hd(single-frame fallback)GET /api/v1/cameras/<index>/snapshot.jpg(opaque browser download)GET /api/v1/recordings/activePOST /api/v1/recordingGET /api/v1/healthPOST /api/v1/health/runPOST /api/v1/cameras(manual onboarding)POST /api/v1/cameras/<index>/updatePOST /api/v1/cameras/<index>/deletePOST /api/v1/discovery/startPOST /api/v1/discovery/stopPOST /api/v1/discovery/clearPOST /api/v1/discovery/addGET /api/v1/analyticsGET /api/v1/archive?camera=&limit=200GET /api/v1/archive/file/<id>POST /api/v1/ptzGET|POST /api/v1/settingsGET /api/v1/users;POST /api/v1/users/create|permissions|delete|passwordPOST /api/v1/sessions/revokeGET /api/v1/logs;POST /api/v1/logs/clearGET /api/v1/diagnostics;GET /api/v1/diagnostics/bundleGET /api/v1/configuration/export;POST /api/v1/configuration/importPOST /api/v1/devices/operation;GET /api/v1/devices/operations/<id>POST /api/v1/devices/majestic/preview|applyPOST /api/v1/camex/preview
Every JSON response has one of these shapes:
{"ok":true,"data":{}}{"ok":false,"error":"Authentication required"}Browser login sets the HttpOnly cookie and does not expose its token to JavaScript. Non-browser API clients may request a Bearer session with {"username":"...","password":"...","sessionMode":"bearer"}; that response includes the token for Authorization: Bearer <session-token> and does not set a cookie. Archive playback supports Range: bytes=... and streams bounded chunks instead of loading a recording into RAM.
When Qt WebSockets is available at build time, the server opens the configured WebSocket port and pushes debounced dashboard snapshots. The web UI falls back to five-second HTTP polling when WebSockets are unavailable or disconnected.
Live preview prefers WebRTC negotiated over the authenticated Dashboard WebSocket. Each occupied monitor cell owns an independent peer connection, so a failed camera does not interrupt the other cells. RTSP credentials remain inside the Dashboard process and are never sent to JavaScript.
For H.264 cameras GStreamer depayloads and reparses the camera stream, then packetizes it for WebRTC without decoding or re-encoding. This preserves the source frame rate and avoids unnecessary latency and CPU load. H.265 cannot be decoded by every browser, so Dashboard transcodes it to H.264 with a bounded low-latency profile: up to 15 FPS for multi-cell SD and 20 FPS for single-cell HD.
The authenticated MJPEG and single-JPEG endpoints remain as a compatibility fallback. The web client switches only the failed cell to MJPEG when WebRTC is unavailable, negotiation times out, or its peer connection fails. Idle fallback pipelines stop automatically after 12 seconds without browser frame requests.
WebRTC signaling messages use the existing WebSocket connection:
- browser to server:
webrtc-start,webrtc-answer,webrtc-ice,webrtc-stop; - server to browser:
webrtc-offer,webrtc-ice,webrtc-status,webrtc-error.
Push-to-talk uses talk-start/talk-stop control messages and bounded binary PCM S16LE/8 kHz
chunks on the same authenticated socket. The server checks the separate Talk permission and camera
scope, permits one speaker owner per camera, rate-bounds PCM and forwards it to Majestic
/play_audio. Browser microphone capture requires a secure context: use localhost or an HTTPS
reverse proxy; plain http://<LAN-IP> cannot request a microphone in modern browsers.
Signaling is authenticated with the same session and Live View permission as the HTTP API. Dashboard currently exchanges host ICE candidates and is intended for localhost, LAN or VPN use. Internet traversal through arbitrary NAT requires a future STUN/TURN configuration layer.
Local archive files are the exception: they are served through the authenticated Dashboard HTTP endpoint because the browser cannot access the desktop filesystem directly.
The browser UI follows the desktop operator workflow instead of presenting a separate card dashboard:
- persistent paged 1, 4 and 9 camera layouts with assignments beyond the visible grid;
- manual page navigation, kiosk/fullscreen wall and optional ten-second page cycling;
- local per-cell digital zoom/pan that does not issue camera PTZ commands;
- camera assignment from the device list and a clear action on every occupied cell;
- a collapsible actions area while the device list remains available;
- camera cards with status, IP address, RTSP port and temperature;
- permission-gated camera discovery, manual onboarding, editing and deletion;
- Health, Analytics and Archive workspaces displayed over the live layout;
- recording, snapshot, audio, push-to-talk, fullscreen and continuous PTZ controls in every monitor cell;
- archive playback and permission-gated download;
- schema-driven Settings, Users/session administration and live log/diagnostics workspaces;
- Camex preview, Majestic read/diff/apply and OpenIPC device status, metrics, network, time and logs;
- explicitly confirmed sync-time, reboot and GitHub update actions with audit and replay protection;
- responsive desktop and mobile layouts with Russian and English localization.
Live preview uses WebRTC first and the authenticated Dashboard JPEG relay as fallback. While GStreamer connects, or if the source stream is unavailable, the cell remains usable and shows camera identity and connection metadata instead of a broken image.
Camera management uses the same NetworkDiscoveryService, CameraModel and transactional state as the desktop UI. Discovery results expose identification evidence and ports but never camera credentials. The edit form also receives no stored login, password or credential-bearing stream URL: blank credential fields preserve the existing backend values, while an explicit checkbox clears them. Manual onboarding sends RTSP paths separately and the server constructs the stored URL after validating the host and ports.
- Configuration uses JSON download/upload instead of native file dialogs. The Web format includes only allowlisted settings, camera hosts/ports/paths and layouts; credentials are omitted and preserved for matching cameras during import.
- Snapshots, recordings, logs and diagnostic bundles use authenticated downloads with opaque identifiers instead of exposing local paths.
- Keychain access remains backend-only. Web shows configured state and write-only credential inputs, never stored secrets.
- Tray controls, native window chrome, printing and OS file associations are
Web N/A. Browser fullscreen, responsive navigation andAlt+1/2/3layout shortcuts replace the transferable parts. - A general SSH terminal is intentionally not exposed. Device administration is a bounded command API with RBAC, validation, confirmation, audit and redaction.
The server adds CSP, X-Frame-Options: DENY, nosniff, no-referrer, same-origin resource policy and no-store headers for API responses. Request headers are limited to 32 KiB and bodies to 1 MiB; chunked request bodies are rejected. Response header names and values are serialized through CR/LF-safe validation. Logs, diagnostics and device responses are bounded and recursively scrubbed. Login, user/settings changes, exports and dangerous device mutations generate AUDIT web events.
The API never returns:
- camera login names or passwords;
- credential-bearing RTSP URLs;
- user password hashes or salts;
- OAuth access/refresh tokens and client secrets;
- local model, evidence or archive filesystem paths.
Archive IDs are SHA-256 identifiers derived from canonical paths. Each playback request resolves the ID again below the configured recording root, preventing direct path input and traversal.
Terminate HTTPS at an explicitly trusted proxy and forward HTTP/WebSocket upstreams to protected Dashboard ports. Dashboard ignores arbitrary forwarded headers: the TCP peer must match the trusted proxy list and browser Origin must match the configured external HTTPS base URL. Use the complete Nginx example and deployment profile checklist.