Skip to content

Commit 2feec0e

Browse files
Sbussisoclaude
andcommitted
docs: align with per-camera recording + per-org timezone (v0.1.43)
Recording page rewritten around the three per-camera modes (Continuous 24/7, Scheduled, Off) with mutual-exclusion, plus the new Time Zone setting and operator-chosen storage cap with disk-aware default at setup time. ApiReference picks up PATCH /api/cameras/{id}/recording-settings and POST /api/settings/timezone; Mcp page swaps the org-level get_recording_settings tool for get/set_camera_recording_policy. Configuration fixes the YAML default and corrects the credential-key crypto description. Dashboard's Settings list now points at the new per-camera + Time Zone sections. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9d9b4e7 commit 2feec0e

5 files changed

Lines changed: 101 additions & 39 deletions

File tree

frontend/src/pages/docs/ApiReference.jsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,16 @@ function ApiReference() {
9797
<p>Create a new node. Returns the API key (shown once).</p>
9898

9999
<div className="docs-endpoint"><span className="docs-endpoint-method get">GET</span><span className="docs-endpoint-path">/api/settings</span></div>
100-
<p>Get recording settings (schedule, continuous mode).</p>
100+
<p>Org-level settings: notifications + timezone. Recording is per-camera since v0.1.43 — see the camera endpoints below.</p>
101+
102+
<div className="docs-endpoint"><span className="docs-endpoint-method post">POST</span><span className="docs-endpoint-path">/api/settings/timezone</span></div>
103+
<p>Set the org's IANA timezone (e.g. <code>"America/Los_Angeles"</code>). Drives the wall-clock interpretation of per-camera scheduled-recording windows. Admin only. 422 on unknown zones.</p>
104+
105+
<div className="docs-endpoint"><span className="docs-endpoint-method patch">PATCH</span><span className="docs-endpoint-path">/api/cameras/{"{camera_id}"}/recording-settings</span></div>
106+
<p>Update a camera's recording policy. Optional fields: <code>continuous_24_7</code>, <code>scheduled_recording</code>, <code>scheduled_start</code>, <code>scheduled_end</code> (HH:MM 24-hour in the org's timezone). The two modes are mutually exclusive — passing both as <code>true</code> returns 422. Admin only.</p>
107+
108+
<div className="docs-endpoint"><span className="docs-endpoint-method post">POST</span><span className="docs-endpoint-path">/api/cameras/{"{camera_id}"}/recording</span></div>
109+
<p>Manual record button — thin wrapper that flips <code>continuous_24_7</code> on the camera. The heartbeat reconciler picks it up within one tick. Body: <code>{"{recording: bool}"}</code>. Admin only.</p>
101110

102111
<div className="docs-endpoint"><span className="docs-endpoint-method get">GET</span><span className="docs-endpoint-path">/api/audit/stream-logs</span></div>
103112
<p>Stream access history. Admin only. Filterable by camera and user.</p>

frontend/src/pages/docs/Configuration.jsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ motion:
6666
cooldown_secs: 30 # minimum seconds between motion events per camera
6767
6868
storage:
69-
max_size_gb: 20 # oldest recordings/snapshots evicted when over`}</code>
69+
max_size_gb: 64 # oldest recordings/snapshots evicted when over;
70+
# the setup wizard suggests a disk-aware default
71+
# (~80% of free disk, 5-64 GB clamp) — operator
72+
# confirms or overrides at install time`}</code>
7073
<button className="docs-copy-btn" onClick={() => copyToClipboard(`node_id: "node_abc123"
7174
api_key: "nak_your_key_here"
7275
api_url: "https://opensentry-command.fly.dev"
@@ -77,16 +80,20 @@ motion:
7780
cooldown_secs: 30
7881
7982
storage:
80-
max_size_gb: 20`)}>Copy</button>
83+
max_size_gb: 64`)}>Copy</button>
8184
</div>
8285

8386
<h3>Credential storage</h3>
8487
<p>
8588
The node API key is encrypted at rest in the SQLite DB using AES-256-GCM
86-
with a machine-derived key (SHA-256 of hostname + application salt). The
87-
database is <strong>not portable</strong> — copying <code>node.db</code> to a
88-
different host will make the stored key unreadable. Re-run <code>setup</code>
89-
after moving to a new machine.
89+
with a machine-derived key — SHA-256 of the OS-managed machine
90+
identifier (<code>/etc/machine-id</code> on Linux,
91+
<code>HKLM\\SOFTWARE\\Microsoft\\Cryptography\\MachineGuid</code> on
92+
Windows, <code>IOPlatformUUID</code> on macOS) plus a domain-separation
93+
tag. These are 128-bit values set once at OS install time, unique per
94+
host, and not user-modifiable. The database is <strong>not portable</strong>
95+
— copying <code>node.db</code> to a different host will make the stored
96+
key unreadable. Re-run <code>setup</code> after moving to a new machine.
9097
</p>
9198

9299
<h3>Resetting a node</h3>

frontend/src/pages/docs/Dashboard.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ function Dashboard() {
2727
<p>Configure your org, nodes, and recording policy. Admin-only.</p>
2828
<ul>
2929
<li><strong>Node Management</strong> — Create nodes, copy API keys at creation time, rotate keys, delete nodes (cascades to cameras).</li>
30-
<li><strong>Recording Settings</strong> — Toggle continuous 24/7 or scheduled recording and define the time window. See the <a href="#recording">Recording</a> section below.</li>
30+
<li><strong>Per-camera recording</strong> — each camera card inside its node has Continuous 24/7 + Scheduled Recording toggles (mutually exclusive). The node card shows a storage usage bar against the operator-chosen cap. See the <a href="#recording">Recording</a> section below.</li>
31+
<li><strong>Time Zone</strong> — set the org's IANA timezone so scheduled-recording windows are interpreted as local wall-clock time, not UTC. DST handled automatically.</li>
3132
<li><strong>Organization</strong> — Invite members, manage roles (Admin vs Member), view resource usage relative to plan caps.</li>
3233
<li><strong>Subscription</strong> — Current plan, usage bars for cameras and nodes, and an upgrade/downgrade flow.</li>
3334
<li><strong>Danger Zone</strong> — Wipe stream logs or perform a full organization reset. Pro/Pro Plus only and each action requires a typed confirmation.</li>

frontend/src/pages/docs/Mcp.jsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,15 @@ function Mcp() {
154154
<div className="docs-mcp-tools">
155155
<div className="docs-endpoint">
156156
<span className="docs-endpoint-method get">READ</span>
157-
<span className="docs-endpoint-path">get_recording_settings</span>
157+
<span className="docs-endpoint-path">get_camera_recording_policy</span>
158158
</div>
159-
<p>The org's recording configuration: whether 24/7 continuous recording is on, whether scheduled recording is on, and the scheduled start/end times. Use when the user asks "are we recording right now?", or before filing an incident if it matters whether the moment was being recorded to disk on the CloudNode.</p>
159+
<p>A single camera's recording policy: <code>continuous_24_7</code>, <code>scheduled_recording</code>, and the scheduled start/end times (HH:MM in the org's timezone). Per-camera since v0.1.43 — replaced the previous org-level <code>get_recording_settings</code>. Use when the user asks "is the garage cam recording right now?" or before filing an incident if it matters whether the moment was being archived locally.</p>
160+
161+
<div className="docs-endpoint">
162+
<span className="docs-endpoint-method post">WRITE</span>
163+
<span className="docs-endpoint-path">set_camera_recording_policy</span>
164+
</div>
165+
<p>Set the recording policy for a specific camera. Any field omitted is left unchanged (PATCH semantics). Use when the user asks "turn on recording for the garage cam" or "set the front door cam to record from 6pm to 6am". Times are HH:MM 24-hour in the org's timezone. Continuous and scheduled are mutually exclusive — passing both as <code>true</code> returns <code>{"{error: 'modes_conflict'}"}</code>.</p>
160166

161167
<div className="docs-endpoint">
162168
<span className="docs-endpoint-method get">READ</span>

frontend/src/pages/docs/Recording.jsx

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,56 +4,95 @@ import { Link } from "react-router-dom"
44
function Recording() {
55
return (
66
<section className="docs-section" id="recording">
7-
<h2>Recording & Retention<a href="#recording" className="docs-anchor">#</a></h2>
7+
<h2>Recording &amp; Retention<a href="#recording" className="docs-anchor">#</a></h2>
88
<p>
9-
Recording in SourceBox Sentry is <strong>node-local by design</strong>. Command Center
10-
sends a policy down to every CloudNode; each node writes its own recordings into
11-
an encrypted SQLite database next to the binary. No video is uploaded for
12-
long-term storage — the cloud holds only the small in-memory segment buffer
13-
needed for live playback.
9+
Recording in SourceBox Sentry is <strong>node-local by design</strong>. Each
10+
camera has its own recording policy; CloudNode reconciles its in-memory
11+
recording state from the backend on every heartbeat (~30 s), and
12+
encrypted recording segments land in the SQLite database next to the
13+
binary. No video is uploaded for long-term storage — the cloud holds
14+
only the small in-memory segment buffer needed for live playback.
1415
</p>
1516

1617
<h3>Recording modes</h3>
18+
<p>
19+
Per-camera since v0.1.43. The two modes are mutually exclusive — turning
20+
one on automatically turns the other off so the operator never has to
21+
reason about a conflicting policy.
22+
</p>
1723
<ul>
18-
<li><strong>Continuous</strong>Every camera records 24/7 while its node is online. Best for commercial deployments with plenty of local disk.</li>
19-
<li><strong>Scheduled</strong>Recording is on only during a defined time window (e.g. 6pm–6am). Useful for residential after-hours coverage.</li>
20-
<li><strong>Manual</strong> — Neither continuous nor scheduled is active. Operators trigger recording on-demand from the dashboard.</li>
24+
<li><strong>Continuous 24/7</strong>the camera records all the time the node is online. Best for high-value coverage and commercial deployments.</li>
25+
<li><strong>Scheduled</strong>the camera records only during a defined wall-clock window (e.g. 18:00–06:00). Useful for residential after-hours coverage, business hours archiving, etc.</li>
26+
<li><strong>Off</strong> (default) — the camera streams live but writes nothing to durable storage.</li>
2127
</ul>
2228

2329
<h3>Configure</h3>
2430
<ol>
25-
<li>Go to <Link to="/settings">Settings</Link> &gt; Recording</li>
26-
<li>Pick a mode (<strong>Continuous</strong>, <strong>Scheduled</strong>, or leave off for manual-only)</li>
27-
<li>For Scheduled, enter the start and end time. Times are in the browser's local timezone, converted to the node's local timezone on delivery.</li>
28-
<li>Save. Nodes pick up the new setting on their next heartbeat (≤30 seconds).</li>
31+
<li>Go to <Link to="/settings">Settings</Link> &gt; Camera Nodes.</li>
32+
<li>Each camera lives inside its node's card, below the storage bar.</li>
33+
<li>Toggle <strong>Continuous 24/7</strong> for always-on, or <strong>Scheduled Recording</strong> for a window.</li>
34+
<li>For Scheduled, the start/end inputs default to <code>08:00–17:00</code>; pick whatever you need.</li>
35+
<li>Nodes pick up the new policy on the next heartbeat (≤30 seconds), and segments start landing in the durable archive on the next 1-second segment rotation after that.</li>
2936
</ol>
3037

31-
<h3>Retention</h3>
38+
<h3>Time zone</h3>
3239
<p>
33-
CloudNode enforces retention by size, not by age. The <code>storage.max_size_gb</code>
34-
config field (default <code>20</code>) is a soft cap — when total stored
35-
recordings + snapshots exceed it, the oldest files are deleted first.
40+
Scheduled-recording windows are interpreted in the org's timezone, not
41+
UTC, so <code>08:00–17:00</code> means 8am to 5pm where the cameras
42+
physically live. Pick the zone in <Link to="/settings">Settings</Link>{" "}
43+
&gt; Time Zone (defaults to UTC for new orgs; one click to use the
44+
browser's detected zone). DST transitions are handled automatically —
45+
a schedule entered in <code>America/Los_Angeles</code> fires at the
46+
correct local hour year-round, no operator intervention at the
47+
spring-forward / fall-back boundaries.
48+
</p>
49+
50+
<h3>Storage cap &amp; retention</h3>
51+
<p>
52+
Each node has a single <strong>storage cap</strong> chosen during the
53+
setup wizard. The wizard reads the host's free disk space and suggests
54+
a sensible default — 80% of free space, clamped to the historical
55+
64 GB ceiling, with a 5 GB floor. A node on a 32 GB Pi SD card gets
56+
~25 GB; a node on a 1 TB drive gets the full 64 GB. The operator can
57+
override during setup or by re-running it.
58+
</p>
59+
<p>
60+
CloudNode enforces the cap every 5 minutes: when total stored bytes
61+
exceed it, oldest recording segments are deleted first (FIFO). The
62+
Settings dashboard surfaces a per-node usage bar with green / amber /
63+
red bands at 75% / 90% so operators can see how full each node is at
64+
a glance.
65+
</p>
66+
<p>
67+
Independently of the cap, CloudNode pauses recording writes when host
68+
free disk drops below a 1 GiB safety floor — a "don't blow up the
69+
host" guardrail that fires regardless of how the cap was set. Live
70+
streaming continues unchanged; only the durable archive is paused.
71+
</p>
72+
73+
<h3>Manual record button</h3>
74+
<p>
75+
The per-camera record button on the dashboard is a thin wrapper that
76+
flips <code>continuous_24_7</code> on the camera. Same end state as
77+
toggling Continuous 24/7 in Settings — the heartbeat reconciler picks
78+
it up within one tick.
3679
</p>
37-
<ul>
38-
<li>Recordings and snapshots are both stored as BLOBs in the encrypted <code>data/node.db</code></li>
39-
<li>Retention is checked after every new recording or snapshot</li>
40-
<li>Adjust the cap to match the disk free on the CloudNode box</li>
41-
</ul>
4280

4381
<h3>Playback</h3>
4482
<p>
45-
Recordings are browsable from the node's local HTTP server on port 8080 —
46-
<code>/recordings/list</code> returns the JSON list and <code>/recordings/&#123;file&#125;</code>
47-
streams the bytes. Typically used from the Command Center dashboard in-app; for
48-
manual access you must be on the same local network as the node.
83+
Recordings are browsable from the node's local HTTP server on port
84+
8080: <code>/recordings/list</code> returns the JSON list, and
85+
<code>/recordings/&#123;file&#125;</code> streams the bytes. Typically
86+
used from the Command Center dashboard in-app; for direct access you
87+
must be on the same local network as the node.
4988
</p>
5089

5190
<div className="docs-callout docs-callout-info">
5291
<p>
5392
<span className="docs-callout-icon">🔒</span>
54-
<span>Because recordings never leave the node, even a Command Center compromise
55-
cannot expose your archive. Protect the node machine with the same rigor you'd
56-
apply to a physical NVR.</span>
93+
<span>Because recordings never leave the node, even a Command Center
94+
compromise cannot expose your archive. Protect the node machine with
95+
the same rigor you'd apply to a physical NVR.</span>
5796
</p>
5897
</div>
5998
</section>

0 commit comments

Comments
 (0)