The live-view <main> now hosts its own 32px footer bar beneath the grid:
prev page on the left, play/stop in the centre, next page + fullscreen on the
right — all icon buttons. Pagination is new: cameras are split into pages of
dim² cells, prev/next walk the pages (page 0 still honours a saved manual
layout), and the arrows disable at the bounds. Fullscreen now targets <main>
so the control bar stays visible in fullscreen (the global stats footer is
outside <main> and hides as before). The sidebar play/fullscreen items now
target <main> too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The camera table actions column is back as the 9th column but stays empty in
the normal view — restart (⟳) and delete (✕) buttons render only on existing
cameras while bulk-editing. Restart in bulk mode no longer reloads the table
(keeps pending edits); delete reloads as before.
Row height is now truly uniform: cams-fixed cells use line-height:1 with
vertically-centred controls, the Вкл/Запись toggles shrink to 30×16, and
selects/inputs/action buttons cap at 20px so filled rows match empty slots.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The camera table actions column (log/restart/delete buttons) is removed:
the table drops to 8 columns across thead, filled rows, empty slots and the
bulk-edit rows. The now-orphaned log subrow and openLogs tracking are gone.
Camera rows get a hard fixed height of 22px via a new table.cams.cams-fixed
rule (zero vertical padding, 22px line-height) so rows never inflate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Header bar dropped from all pages; brand, system stats (#sys-stats) and clock
(#clock) now live in the 22px footer next to the version. .layout fills the
full height minus the footer. ensureFooter runs before startClock/refreshStats
so the moved elements exist when populated.
VERSION 0.0.136.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Footer is a third layout block; .layout height now subtracts --footer-h (22px)
so it never overlaps the full-height video grid. Injected via JS only on pages
with the main layout (skips login); shows CoRE.Vision brand + version.
VERSION 0.0.135.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Изменить" now renders all 64 slots as editable rows — filled ones for editing,
empty ones with blank fields (user prefilled "admin", channel 1) for adding.
Saving PUTs changed cameras and POSTs filled empty slots (auto id camN, lowest
free). Removed the per-row "+ Добавить" buttons; empty slots in normal view just
show "пусто". Bulk edit can now be entered with zero cameras.
VERSION 0.0.134.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adding a camera stays available via the per-slot "+ Добавить" row. JS handlers
are already guarded with if (el), so no logic changes are needed.
VERSION 0.0.133.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cameras (esp. an NVR at one IP with many channels) are configured by a channel
number now: channel N → main /Streaming/Channels/{N*100+1}, sub {N*100+2}
(101/102 = ch1, 1101/1102 = ch11, 3201/3202 = ch32). The add/edit form and the
bulk-edit row use a 1-32 dropdown; the camera list shows "канал N". Frontend
derives main_path/sub_path from the channel, so the backend is unchanged.
VERSION 0.0.132.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Изменить" previously replaced each camera row with a vertical .cam-edit-grid
form inside one colspan cell — effectively a different table. Now bulk edit
keeps the same columns and turns the cells into compact inline inputs:
Камера→name, IP→ip, Потоки→main/sub, Вкл/Запись toggles, Хранилище select,
and the Статус column becomes Логин/Пароль (header swaps via data-bulk-label).
Live per-row onchange is suppressed while bulk is active (changes collected
until Сохранить). Inputs are compact (22px) so row height stays dense.
VERSION 0.0.131.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-row "Хранилище" <select> inherited the base select padding (6px 8px,
~32px tall) and inflated every camera row. Scope a compact rule to .stor-sel
(22px height, matching the toggles) so row height stays dense. Edit/bulk-form
selects are unaffected.
VERSION 0.0.130.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Camera.storage_id: each camera chooses its storage ("" = active/default).
supervisor resolves per-camera (storage_by_id → active fallback); cameras
API (body/payload) + /toggle accept storage_id and repoint the recorder.
- Recordings now go to <root>/<cam>/<YYYY-MM-DD>/<file>.mp4. This ffmpeg build
lacks -strftime_mkdir, so recorder_task pre-creates today+tomorrow dirs at
start and the indexer re-ensures them each tick (survives midnight).
- indexer: scan date subdirs + legacy flat files; sort by basename so the
in-progress segment is detected correctly when both layouts coexist.
- frontend: per-camera "Хранилище" dropdown in the camera table (inline) and
in the add/edit + bulk forms. Move the active-storage summary widget from
the Система tab into the Хранилища tab.
VERSION 0.0.129.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the single fixed /records path with a list of storages and an
active selection (where new recordings go). Quota is now per-storage.
- config: StorageItem(id,name,type,path,quota_gb,nas-meta) + Storage keeps
global retention_days/segment_seconds; active + items. Migration from the
old single {path,quota_gb} schema → one local item.
- recorder: write to the active storage; restart_all() repoints on switch;
NAS-not-mounted guard (never write into the container overlay).
- indexer: scan ALL storage roots so recordings survive an active switch.
- retention: enforce quota PER storage via db.total_size_under /
oldest_recordings_under (path-prefix LIKE).
- api/storages: GET/POST/PUT/DELETE + activate + policy. NAS uses host-mount
(backend stores SMB params, generates the mount/fstab command, reports
ismount status). Retire old POST /api/storage.
- frontend: new "Хранилища" settings tab — table with inline per-row quota,
add form (local folder / NAS SMB), global policy (segment + retention).
- compose: add shared ./storages:/storages root for extra local folders and
host-mounted NAS shares.
VERSION 0.0.127.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a separate `record` field so a camera can be registered but fully
off, or on but not recording:
- enabled (Вкл): camera on/off. Disabled = no go2rtc stream (no live),
no recording; entry is just stored. build_streams/sync_all/reconcile
skip disabled cameras; live grid shows "камера выключена".
- record (REC): write segments to disk. Recorder runs only when
enabled AND record.
Unified POST /api/cameras/{id}/toggle {enabled?, record?} (replaces the
/record endpoint); go2rtc is touched only when `enabled` changes, so
flipping REC doesn't blip live. Camera table gets Вкл + Запись toggle
columns; add/edit forms get both switches.
load_config migrates the old schema (no `record` key): old `enabled`
meant "record" with live always on -> enabled=True, record=old enabled.
VERSION -> 0.0.126.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`enabled` already gated recording only (go2rtc/live work regardless), but
it was a buried "Вкл" checkbox. Make it an explicit REC control:
- New "Запись" column with an inline REC switch in each camera row.
- Relabel the add/edit form toggle "Вкл" -> "REC" + tooltip.
- New POST /api/cameras/{id}/record {enabled} that flips recording through
the supervisor only (no go2rtc re-sync, so live view doesn't blip).
Off = view-only (live via go2rtc); on = record segments to disk.
VERSION -> 0.0.124.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The params row was a ragged flex-wrap and "Готовый профиль" was floated
with margin-left:auto, so controls didn't line up and the preset menu
dangled at an odd height. Split the row into a left grid (auto-fit
columns, controls bottom-aligned, full-width) and a separate right-side
"Готовый профиль" zone. Reorder fields logically: codec, H.264 profile,
preset, rate control, bitrate, QP, keyframe interval. Drop the now-dead
SOFT/MEDIUM/HARD button CSS.
VERSION -> 0.0.123.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The SOFT/MEDIUM/HARD selector lost its color coding when it became a
dropdown. Re-apply colors (SOFT red, MEDIUM yellow, HARD white) to the
options and to the select's shown value, and right-align the "Готовый
профиль" label (margin-left:auto) so it clearly sits on the right.
VERSION -> 0.0.122.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Double-clicking a grid cell now maximizes that camera ("solo") via a CSS
class on the grid instead of putting the tile into the browser Fullscreen
API. In fullscreen the soloed camera fills the screen; double-clicking
again returns to the grid (staying fullscreen). From windowed mode the
gesture also enters fullscreen so the camera fills the screen. ESC exits
fullscreen natively; on exit the solo state is cleared.
VERSION -> 0.0.121.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Style QP <input type=number> like the <select>s (it rendered with the
default white background -> stood out); add a disabled (greyed) style.
- QP vs bitrate are now mutually DISABLED instead of hidden: QP mode
greys the bitrate field, VBR/CBR greys the QP field. Hiding never
worked because .tr-row label{display:flex} overrode [hidden].
- Replace SOFT/MEDIUM/HARD preset buttons with a "Готовый профиль"
dropdown, placed last (rightmost) in the params row.
VERSION -> 0.0.120.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Player/transcoding settings gain encoder controls:
- Rate-control mode VBR / CBR / QP (constant quantizer). x264: VBR uses
-b:v/-maxrate/-bufsize, CBR adds -x264-params nal-hrd=cbr (min=max=b:v),
QP uses -qp N (no bitrate). VP9 maps QP -> -crf N -b:v 0, CBR -> min=max=b:v.
- Preset list expanded to ultrafast/superfast/veryfast/faster/fast/medium
(was only ultrafast/superfast); for VP9 the preset maps to cpu-used.
- New QP field in the UI, shown when rate control = QP, otherwise the
bitrate field is shown.
Both /play.mp4 (archive) and /live.mp4 accept rc + qp query params.
All three modes verified producing valid output on the live restream.
VERSION -> 0.0.119.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per request, move the account widget (current user line + logout button)
to the top of the "Пользователи" tab and remove the standalone Account
tab. Note: the Users tab is superadmin-only, so logout is now reachable
only by superadmin (it is the only logout in the app) -- confirmed
acceptable by the user, who operates as superadmin.
VERSION -> 0.0.118.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Система" tab was a catch-all (Recording, Player & transcoding,
Storage, System info, Account). Split out two dedicated tabs:
- "Плеер": per-browser playback/transcode profiles. Kept at operator+
(the transcode option spawns server-side ffmpeg, i.e. CPU load).
- "Учётная запись": current user + logout. Ungated, so any signed-in
user (incl. viewers) can log out -- previously it was buried inside
the operator-only "Система" tab.
.tabs gets flex-wrap so the now-7 tabs wrap instead of overflowing.
VERSION -> 0.0.117.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The built-in WASM player (frontend/static/vendor/hevcjs/hevc-core.js)
rendered washed-out, green/red-shifted video. Two causes, both fixed:
- VideoFrame path (Chrome): the I420 VideoFrame was created without a
colorSpace, so the browser defaulted to BT.601 limited -> hue shift,
and guessed the range -> washout. Set explicit BT.709, fullRange:false.
- WebGL fallback shader: used BT.709 FULL-range coefficients on
limited-range camera output -> washout/desaturation. Switched to
BT.709 limited-range conversion ((Y-16)*255/219, chroma centered 128).
VERSION -> 0.0.116.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live and recording both read from the go2rtc restream. If go2rtc's
runtime stream table gets emptied (camera clear via API, go2rtc restart
with a stale/empty config, or a silently-failed startup sync_all), every
restream returns 404 and both live and recording go dark until a manual
`docker restart nvr-go2rtc`.
Add a background Reconciler that every 30s diffs the desired streams
against go2rtc /api/streams and PUTs only the missing ones (existing
streams are left untouched so active producers/consumers are not
disrupted). VERSION -> 0.0.115.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
entrypoint.sh has a /bin/sh shebang; CRLF would break exec in the Linux
container. Keep shell scripts LF-only regardless of Windows autocrlf.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>