storage: multi-storage management with per-storage quota
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>
This commit is contained in:
+81
-15
@@ -34,6 +34,7 @@
|
||||
<main class="settings-main">
|
||||
<div class="tabs">
|
||||
<button class="tab active" data-tab="cams" data-min-role="admin">Камеры</button>
|
||||
<button class="tab" data-tab="storage" data-min-role="admin">Хранилища</button>
|
||||
<button class="tab" data-tab="layout">Раскладка</button>
|
||||
<button class="tab" data-tab="player" data-min-role="operator">Плеер</button>
|
||||
<button class="tab" data-tab="users" data-min-role="superadmin">Пользователи</button>
|
||||
@@ -62,25 +63,90 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ВКЛАДКА: Хранилища (управление записью) -->
|
||||
<div class="tab-panel" data-panel="storage" data-min-role="admin" hidden>
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<h2>Хранилища записи</h2>
|
||||
<button id="stor-add">+ Добавить хранилище</button>
|
||||
</div>
|
||||
<div class="muted" style="margin-bottom:12px">
|
||||
Активное хранилище — куда пишутся новые записи. Квота задаётся на каждое хранилище
|
||||
(0 = без лимита) и правится прямо в таблице. После смены активного старые записи
|
||||
остаются доступны в Архиве. Удаление хранилища убирает его из списка — файлы записей
|
||||
НЕ удаляются.
|
||||
</div>
|
||||
<table class="cams">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:74px">Активно</th>
|
||||
<th style="width:90px">ID</th>
|
||||
<th>Название</th>
|
||||
<th style="width:56px">Тип</th>
|
||||
<th>Путь</th>
|
||||
<th style="width:130px">Квота, ГБ</th>
|
||||
<th style="width:150px">Занято / диск</th>
|
||||
<th style="width:130px">Статус</th>
|
||||
<th style="width:48px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="stor-rows"></tbody>
|
||||
</table>
|
||||
<div class="muted" id="stor-msg" style="margin-top:10px;font-size:12px"></div>
|
||||
</div>
|
||||
|
||||
<!-- форма добавления (скрыта по умолчанию) -->
|
||||
<div class="card" id="stor-form-card" hidden>
|
||||
<h2>Новое хранилище</h2>
|
||||
<div class="cam-edit-grid">
|
||||
<label>ID<input type="text" id="sf-id" placeholder="arch2"></label>
|
||||
<label>Название<input type="text" id="sf-name" placeholder="Архив 2"></label>
|
||||
<label>Тип
|
||||
<select id="sf-type">
|
||||
<option value="local">Локальное (папка)</option>
|
||||
<option value="nas">NAS (SMB)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Квота, ГБ (0 = без лимита)<input type="number" id="sf-quota" min="0" value="0"></label>
|
||||
<label class="sf-local">Папка<input type="text" id="sf-path" placeholder="/storages/arch2 (по умолчанию)"></label>
|
||||
<label class="sf-nas" hidden>SMB сервер (IP)<input type="text" id="sf-server" placeholder="192.168.1.10"></label>
|
||||
<label class="sf-nas" hidden>Шара (share)<input type="text" id="sf-share" placeholder="cctv"></label>
|
||||
<label class="sf-nas" hidden>Логин<input type="text" id="sf-user"></label>
|
||||
<label class="sf-nas" hidden>Пароль<input type="password" id="sf-pass"></label>
|
||||
<label class="sf-nas" hidden>Подпапка (необяз.)<input type="text" id="sf-subpath" placeholder="cam-archive"></label>
|
||||
</div>
|
||||
<div class="cam-edit-foot">
|
||||
<span class="err" id="sf-err"></span>
|
||||
<button id="sf-save" class="primary">Создать</button>
|
||||
<button id="sf-cancel">Отмена</button>
|
||||
</div>
|
||||
<div class="muted" id="sf-mount" style="margin-top:10px;font-size:12px;white-space:pre-wrap"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Политика записи <span class="muted" style="font-weight:400;font-size:12px">— общая для всех хранилищ</span></h2>
|
||||
<div class="cam-edit-grid">
|
||||
<label>Длина сегмента
|
||||
<select id="pol-segment">
|
||||
<option value="60">1 минута</option>
|
||||
<option value="120">2 минуты</option>
|
||||
<option value="300">5 минут</option>
|
||||
<option value="600">10 минут</option>
|
||||
<option value="900">15 минут</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Срок хранения, дней (0 = не удалять по возрасту)<input type="number" id="pol-retention" min="0" max="3650"></label>
|
||||
</div>
|
||||
<div style="margin-top:12px"><button id="pol-save">Сохранить</button></div>
|
||||
<div class="muted" id="pol-saved" style="margin-top:8px;font-size:12px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ВКЛАДКА 2: Система -->
|
||||
<div class="tab-panel" data-panel="general" data-min-role="operator" hidden>
|
||||
<div class="wrap">
|
||||
<div class="card" data-min-role="admin">
|
||||
<h2>Запись</h2>
|
||||
<label class="fl" style="font-size:13px;color:var(--text)">Размер сегмента</label>
|
||||
<select id="segment-size" style="margin-top:6px;display:block">
|
||||
<option value="60">1 минута</option>
|
||||
<option value="120">2 минуты</option>
|
||||
<option value="300">5 минут</option>
|
||||
<option value="600">10 минут</option>
|
||||
<option value="900">15 минут</option>
|
||||
</select>
|
||||
<div style="margin-top:12px"><button id="segment-save">Сохранить</button></div>
|
||||
<div class="muted" id="segment-saved" style="margin-top:8px;font-size:12px"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Хранилище</h2>
|
||||
<h2>Хранилище <span class="muted" style="font-weight:400;font-size:12px">— активное (управление во вкладке «Хранилища»)</span></h2>
|
||||
<div class="kv" id="storage-kv"><div class="muted">загрузка…</div></div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user