Глобально уникальные 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>
49 lines
3.3 KiB
HTML
49 lines
3.3 KiB
HTML
{% extends "base.html" %}
|
||
{% import "_ui.html" as ui %}
|
||
{% block content %}
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>Журнал</h1>
|
||
<div class="sub">{{ st.count|num }} {{ st.count|plural("запись", "записи", "записей") }} · старейшая {{ st.oldest|dt }} · ротация: {{ rotation }}</div>
|
||
</div>
|
||
<div class="actions">
|
||
<a class="btn secondary" href="/events{{ '?' ~ qs if qs }}">{{ ui.icon("refresh") }}Обновить</a>
|
||
<button type="button" class="btn secondary" hx-get="/ui/dialog/events-settings" hx-target="#modal-body">{{ ui.icon("settings") }}Настройки</button>
|
||
<button type="button" class="btn danger" hx-get="/ui/dialog/events-clear" hx-target="#modal-body">{{ ui.icon("trash") }}Очистить журнал</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="tools">
|
||
<form method="get" action="/events">
|
||
<select class="field {{ 'on' if flt.type }}" name="type" onchange="this.form.submit()" aria-label="Тип" style="width:170px">
|
||
<option value="">Тип: любой</option>
|
||
{% for c in categories %}<option value="{{ c }}" {{ "selected" if flt.type == c }}>Тип: {{ c }}</option>{% endfor %}
|
||
{% for t in types %}<option value="{{ t }}" {{ "selected" if flt.type == t }}>Тип: {{ t }}</option>{% endfor %}
|
||
</select>
|
||
<select class="field {{ 'on' if flt.actor }}" name="actor" onchange="this.form.submit()" aria-label="Актор" style="width:170px">
|
||
<option value="">Актор: любой</option>
|
||
{% for a in actors %}<option value="{{ a }}" {{ "selected" if flt.actor == a }}>Актор: {{ a }}</option>{% endfor %}
|
||
</select>
|
||
<select class="field {{ 'on' if flt.device }}" name="device" onchange="this.form.submit()" aria-label="Устройство" style="width:200px">
|
||
<option value="">Устройство: любое</option>
|
||
{% for d in devices %}<option value="{{ d.id }}" {{ "selected" if flt.device == d.id }}>Устройство: {{ d.name }}</option>{% endfor %}
|
||
</select>
|
||
<label class="datefield">с <input type="date" name="date_from" value="{{ flt.date_from }}" onchange="this.form.submit()" aria-label="Дата с"></label>
|
||
<label class="datefield">по <input type="date" name="date_to" value="{{ flt.date_to }}" onchange="this.form.submit()" aria-label="Дата по"></label>
|
||
<label class="search">{{ ui.icon("search") }}<input type="search" name="q" value="{{ flt.q }}" placeholder="Сообщение или ID" aria-label="Поиск по сообщению или ID" style="width:240px"></label>
|
||
<span class="grow"></span>
|
||
{% if flt_active %}<a class="btn ghost" href="/events">Сбросить ({{ flt_active }})</a>{% endif %}
|
||
</form>
|
||
</div>
|
||
<div class="table-wrap">
|
||
<table class="grid events">
|
||
<colgroup><col style="width:176px"><col style="width:180px"><col style="width:150px"><col><col style="width:110px"><col style="width:96px"></colgroup>
|
||
<thead><tr><th>Время (UTC)</th><th>Тип</th><th>Сущность</th><th>Сообщение</th><th>Актор</th><th>ID записи</th></tr></thead>
|
||
<tbody id="events-body">{% include "_events_rows.html" %}</tbody>
|
||
</table>
|
||
</div>
|
||
{% include "_events_more.html" %}
|
||
</div>
|
||
{% endblock %}
|