Глобально уникальные 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>
21 lines
1.9 KiB
HTML
21 lines
1.9 KiB
HTML
{% import "_ui.html" as ui %}
|
|
<form method="post" action="/events/clear" {% if in_modal %}hx-post="/events/clear" hx-target="#modal-body"{% endif %}>
|
|
<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">
|
|
<div class="note">Будет удалено <b>{{ st.count|num }} {{ st.count|plural("запись", "записи", "записей") }}</b> без возможности восстановления. В журнале останется одна запись об очистке: кто и когда её выполнил.</div>
|
|
<label class="lbl"><span>Пароль пользователя <b style="font-weight:600">{{ user }}</b> <small>подтверждение нужно только для этого действия</small></span>
|
|
<input class="input {{ 'invalid' if error }}" type="password" name="password" placeholder="Введите пароль" autocomplete="current-password"
|
|
data-enables="#clear-confirm" {{ "disabled" if locked else "autofocus" }} {% if error %}aria-invalid="true" aria-describedby="clear-error"{% endif %}></label>
|
|
{% if locked %}<div class="form-err" role="alert">Слишком много неверных попыток. Повторите через {{ minutes }} мин.</div>
|
|
{% elif error %}<div class="field-err" id="clear-error" role="alert">{{ error }}</div>{% endif %}
|
|
</div>
|
|
<footer class="modal-foot">
|
|
<span class="grow"></span>
|
|
{% if in_modal %}<button type="button" class="btn ghost-n" data-close>Отмена</button>{% else %}<a class="btn ghost-n" href="/events">Отмена</a>{% endif %}
|
|
<button class="btn danger" id="clear-confirm" disabled>{{ ui.icon("trash") }}Очистить журнал</button>
|
|
</footer>
|
|
</form>
|