Глобально уникальные ID (docs/changes/016): - у устройств, групп, задач, резервных копий и записей журнала ID вида <префикс>_<uuid7> (dev_, grp_, job_, bkp_, evt_): типы не пересекаются, внутри типа ID не повторяются и сортируются по времени; - миграция БД v1 заменяет числовые ID с пересчётом ссылок (копия файла БД перед миграцией, сверка числа строк, одна транзакция); - резервная копия = пара файлов с одним bkp_ ID, файлы в S3 получают метаданные backup-id/device-id; синхронизация метаданных с бакетом; - журнал событий в БД (events): создание/изменение/удаление, задачи, бэкапы, смена online/offline, вход в UI; API чтения GET /api/v1/events. Журнал в UI, ротация и очистка (docs/changes/017): - страница «Журнал»: фильтры, подгрузка «Показать ещё», окно записи; - настройки ротации (срок и максимум записей) хранятся в БД (схема v2), ротация при старте, раз в час и после сохранения настроек; - очистка журнала только через окно с паролем пользователя, блокировка после 5 неверных попыток, остаётся запись о факте очистки; через API очистки нет. Тесты: 20 из 20. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
24 lines
2.0 KiB
HTML
24 lines
2.0 KiB
HTML
{% import "_ui.html" as ui %}
|
||
<div class="w640">
|
||
<header class="modal-head">
|
||
<h2 id="modal-title">Запись журнала</h2>
|
||
{% if in_modal %}<button type="button" class="btn ghost-n icon" data-close aria-label="Закрыть">{{ ui.icon("x") }}</button>{% endif %}
|
||
</header>
|
||
<div class="modal-body">
|
||
{% macro idcell(value) -%}<span class="mono">{{ value }}</span><button type="button" class="btn ghost-n icon" data-copy="{{ value }}" aria-label="Копировать" title="Копировать">{{ ui.icon("copy") }}</button>{%- endmacro %}
|
||
<div class="kv"><span>ID записи</span><div>{{ idcell(e.id) }}</div></div>
|
||
<div class="kv"><span>Время</span><div>{{ e.ts|dts }} UTC</div></div>
|
||
<div class="kv"><span>Тип</span><div><span class="badge {{ ev_kind(e.type) }}">{{ e.type }}</span></div></div>
|
||
<div class="kv"><span>Актор</span><div>{{ e.actor }}</div></div>
|
||
<div class="kv"><span>Сущность</span><div>{% if e.entity_id %}{{ ev_entity(e.entity_type) }} · {{ idcell(e.entity_id) }}{% else %}<span class="muted">{{ ev_entity(e.entity_type) }}</span>{% endif %}</div></div>
|
||
<div class="kv"><span>Устройство</span><div>{% if e.device_id %}{% if device %}{{ device.name }} · {% endif %}{{ idcell(e.device_id) }}{% if not device %}<span class="muted">(удалено)</span>{% endif %}{% else %}<span class="muted">—</span>{% endif %}</div></div>
|
||
<div class="kv"><span>Задача</span><div>{% if e.job_id %}{{ idcell(e.job_id) }}{% else %}<span class="muted">—</span>{% endif %}</div></div>
|
||
<div class="kv"><span>Сообщение</span><div>{{ e.message or "—" }}</div></div>
|
||
{% if data_json %}<div style="font:500 13px/1.3 var(--font)">Данные</div><pre class="json">{{ data_json }}</pre>{% endif %}
|
||
</div>
|
||
<footer class="modal-foot">
|
||
<span class="grow"></span>
|
||
{% if in_modal %}<button type="button" class="btn secondary" data-close>Закрыть</button>{% else %}<a class="btn secondary" href="/events">К журналу</a>{% endif %}
|
||
</footer>
|
||
</div>
|