| Time | Topic | Source | Payload |
|---|---|---|---|
| No events yet | |||
| {formatTime(ev.received_at)} | {ev.topic} | {ev.source_type} | |
| Name | Type | Streams | Status |
|---|---|---|---|
| No cameras configured | |||
| {cam.name} | {cam.type.toUpperCase()} | {String(props.streamCounts.get(cam.id) ?? 0)} | {cam.enabled ? Enabled : Disabled } |
Connect to an ONVIF camera or NVR by host and credentials. Profiles from the same video source are imported as streams on one camera.
Video sources reported by {props.host}. Each source imports as one camera with its profiles saved as streams.
| Profile | Encoding | Resolution | FPS | Stream URI | |
|---|---|---|---|---|---|
| No profiles returned | |||||
| {p.profile_name} | {p.encoding ? {p.encoding} : "—"} | {p.width && p.height ? `${String(p.width)}x${String(p.height)}` : "—"} | {p.framerate != null ? String(p.framerate) : "—"} | {p.stream_uri} | |
Video sources reported by {props.host}. Each source imports as one camera with its profiles saved as streams.
{props.cameras.length === 0 ? (Entities are reusable content blocks (a camera reference, an HTML snippet, a web page, or a Node-RED dashboard tab). Bind one entity to any number of layout cells — edit the entity once and every cell updates.
| Name | Type | Detail |
|---|---|---|
| No entities yet | ||
| {e.name} | {entityBadge(e.type)} | {entityDetail(e)} |
Auto-synced from Node-RED. Press Sync Dashboards after adding or renaming tabs in Node-RED. Editing a dashboard happens in the Node-RED editor.
| Name | Tab ID | URL |
|---|---|---|
| No dashboards synced yet — press Sync. | ||
| {e.name} | {e.dashboard_id ?? "—"} | {e.dashboard_id ? `/dash/${e.dashboard_id}` : "—"} |
| Name | Hardware | Last Seen | Status |
|---|---|---|---|
| No kiosks paired | |||
| {k.name} | {k.hardware_model ?? "—"} | {k.last_seen_at ? formatTime(k.last_seen_at) : "Never"} | {k.enabled ? Active : Disabled } |
{pc.code}
expires {formatTime(pc.expires_at)}
{pc.kiosk_proposed_name}> : "(no name)"}
{pc.kiosk_hardware_model ? <> · hw: {pc.kiosk_hardware_model}> : null}
{managed ? <> · managed image> : null}
Enabled {" "}TOTP is active on this account.
Protect your account with a TOTP authenticator app.
Scan this with your authenticator app (Google Authenticator, Authy, etc.).
{props.secret}
Save these codes somewhere safe. They will not be shown again.
{props.description}
| Name | Details | |
|---|---|---|
| None configured yet | ||
| {item.name} {item.badge && ( {item.badge} )} | {item.detail ?? ""} | |
No labels attached
)}| Role | Name | URI |
|---|---|---|
| {s.role} | {s.name} | {s.rtsp_uri} |
No streams configured
)}Kiosks whose layouts reference this camera. Snapshots are pulled from a subscribed kiosk (same LAN as camera) when available.
{props.subscriptions.length > 0 ? (| Kiosk | Layouts | Status |
|---|---|---|
| {sub.kiosk.name} | {sub.layouts.join(", ") || "—"} | {sub.active ? active : bundled} |
No kiosk has this camera in any layout.
)}No labels attached
)}| Name | Resolution | Index | Power |
|---|---|---|---|
| {d.name} | {String(d.width_px)}x{String(d.height_px)} | {String(d.index)} | {powerBadge(d.actual_power_state)} |
No displays associated with this kiosk
)}
Each input binding fires an event with the configured topic when the
pin's edge triggers. Pi 5's main GPIO chip is gpiochip4;
older Pis use gpiochip0.
| Chip | Pin | Dir | Pull | Edge | Topic | |
|---|---|---|---|---|---|---|
| {g.chip} | {String(g.pin)} | {g.direction} | {g.pull ?? "—"} | {g.edge ?? "—"} | {g.topic} |
No GPIO bindings configured
)}| Time | Level | Message |
|---|---|---|
| {log.received_at.replace("T", " ").replace(/\.\d+Z$/, "Z")} | {log.level} |
{log.message}
{ctx && {ctx}}
|
No logs received from this kiosk
)}| Name | Color | Actions |
|---|---|---|
| No labels | ||
| {l.name} | {l.color ? {l.color} : "—"} | |
Layouts are standalone — they define a grid of regions and bind cameras or other content into them. Attach a layout to one or more displays from the display's edit page.
| Name | Displays | Priority |
|---|---|---|
| No layouts created yet | ||
| {l.name} | {count === 0 ? unattached : {String(count)} display{count !== 1 ? "s" : ""}} | {l.priority === "hot" ? hot : l.priority === "cold" ? cold : normal } |
Create an empty layout. You'll add cells visually on the next page, then attach the layout to one or more displays.
Hover a side + to add a neighbour or expand the cell. Expanding pushes cells in that direction out of the way. Click a cell to edit content in-place.