Админка стала удобной с телефона
Навигация сворачивается в бургер-меню, второстепенные колонки таблиц скрываются на узких экранах, формы и тулбар редактора нормально складываются в одну колонку. Основные действия и ссылки остаются доступны на ширине 360–390 px.
This commit is contained in:
parent
1c85590f6f
commit
cfa0e29bbf
|
|
@ -96,6 +96,9 @@ ID пользователя ведёт в его карточку. ClickHouse ч
|
||||||
код, блок кода, цитата, спойлер и ссылка; есть предпросмотр и счётчик символов
|
код, блок кода, цитата, спойлер и ссылка; есть предпросмотр и счётчик символов
|
||||||
(1024 для подписи к фото, 4096 для обычного сообщения).
|
(1024 для подписи к фото, 4096 для обычного сообщения).
|
||||||
|
|
||||||
|
Интерфейс адаптирован под мобильные: бургер-меню, второстепенные колонки таблиц
|
||||||
|
скрываются на узких экранах, формы складываются в одну колонку.
|
||||||
|
|
||||||
## Аналитика (ClickHouse)
|
## Аналитика (ClickHouse)
|
||||||
|
|
||||||
Все значимые действия бота пишутся в ClickHouse в единую таблицу `analytics.events`.
|
Все значимые действия бота пишутся в ClickHouse в единую таблицу `analytics.events`.
|
||||||
|
|
@ -158,7 +161,7 @@ make backup E=test # на домашнем сервере: ./scripts/clickho
|
||||||
|
|
||||||
## Версионирование
|
## Версионирование
|
||||||
|
|
||||||
- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.5.2`.
|
- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.5.3`.
|
||||||
- Схема — SemVer-подобная (`MAJOR.MINOR.PATCH`); git-тег = `v` + версия (`v0.1`).
|
- Схема — SemVer-подобная (`MAJOR.MINOR.PATCH`); git-тег = `v` + версия (`v0.1`).
|
||||||
- Docker-образы тегируются версией (Блок 2), события ClickHouse получают `bot_version` (Блок 3).
|
- Docker-образы тегируются версией (Блок 2), события ClickHouse получают `bot_version` (Блок 3).
|
||||||
- Выпуск: изменить `common/version.py` → коммит → аннотированный тег `vX.Y` → `git push --tags`.
|
- Выпуск: изменить `common/version.py` → коммит → аннотированный тег `vX.Y` → `git push --tags`.
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
<th class="px-4 py-3 font-medium">Username</th>
|
<th class="px-4 py-3 font-medium">Username</th>
|
||||||
<th class="px-4 py-3 font-medium">Роль</th>
|
<th class="px-4 py-3 font-medium">Роль</th>
|
||||||
<th class="px-4 py-3 font-medium">Доступ</th>
|
<th class="px-4 py-3 font-medium">Доступ</th>
|
||||||
<th class="px-4 py-3 font-medium">Последний вход</th>
|
<th class="hidden px-4 py-3 font-medium lg:table-cell">Последний вход</th>
|
||||||
<th class="px-4 py-3 font-medium"></th>
|
<th class="px-4 py-3 font-medium"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -116,7 +116,7 @@
|
||||||
<span class="rounded bg-slate-100 px-2 py-0.5 text-xs text-slate-500">выключен</span>
|
<span class="rounded bg-slate-100 px-2 py-0.5 text-xs text-slate-500">выключен</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-slate-600">
|
<td class="hidden px-4 py-3 text-slate-600 lg:table-cell">
|
||||||
{{ admin.last_login_at.strftime("%d.%m.%Y %H:%M") if admin.last_login_at else "—" }}
|
{{ admin.last_login_at.strftime("%d.%m.%Y %H:%M") if admin.last_login_at else "—" }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
|
|
|
||||||
|
|
@ -9,26 +9,39 @@
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen bg-slate-100 text-slate-900">
|
<body class="min-h-screen bg-slate-100 text-slate-900">
|
||||||
<header class="border-b border-slate-200 bg-white">
|
<header class="border-b border-slate-200 bg-white">
|
||||||
<div class="mx-auto flex max-w-5xl items-center justify-between px-4 py-4">
|
<div class="mx-auto max-w-5xl px-4 py-3">
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||||
<a href="/" class="text-lg font-semibold">DispelEvilBot</a>
|
<a href="/" class="text-lg font-semibold">DispelEvilBot</a>
|
||||||
<nav class="flex items-center gap-4 text-sm">
|
<button
|
||||||
<a href="/" class="text-slate-600 hover:text-slate-900">Главная</a>
|
id="nav-toggle"
|
||||||
<a href="/messages" class="text-slate-600 hover:text-slate-900">Сообщения</a>
|
type="button"
|
||||||
<a href="/sequences" class="text-slate-600 hover:text-slate-900">Последовательности</a>
|
class="rounded-lg border border-slate-300 px-3 py-1.5 text-sm text-slate-600 sm:hidden"
|
||||||
<a href="/schedules" class="text-slate-600 hover:text-slate-900">Расписания</a>
|
aria-expanded="false"
|
||||||
<a href="/users" class="text-slate-600 hover:text-slate-900">Пользователи</a>
|
>
|
||||||
<a href="/admins" class="text-slate-600 hover:text-slate-900">Админы</a>
|
Меню
|
||||||
<a href="/logs" class="text-slate-600 hover:text-slate-900">Логи</a>
|
</button>
|
||||||
|
<nav
|
||||||
|
id="nav-menu"
|
||||||
|
class="hidden w-full flex-col gap-2 border-t border-slate-100 pt-2 text-sm sm:flex sm:w-auto sm:flex-row sm:items-center sm:gap-4 sm:border-0 sm:pt-0"
|
||||||
|
>
|
||||||
|
<a href="/" class="py-1 text-slate-600 hover:text-slate-900">Главная</a>
|
||||||
|
<a href="/messages" class="py-1 text-slate-600 hover:text-slate-900">Сообщения</a>
|
||||||
|
<a href="/sequences" class="py-1 text-slate-600 hover:text-slate-900">Последовательности</a>
|
||||||
|
<a href="/schedules" class="py-1 text-slate-600 hover:text-slate-900">Расписания</a>
|
||||||
|
<a href="/users" class="py-1 text-slate-600 hover:text-slate-900">Пользователи</a>
|
||||||
|
<a href="/admins" class="py-1 text-slate-600 hover:text-slate-900">Админы</a>
|
||||||
|
<a href="/logs" class="py-1 text-slate-600 hover:text-slate-900">Логи</a>
|
||||||
{% if request.session.get('admin_id') %}
|
{% if request.session.get('admin_id') %}
|
||||||
<span class="text-slate-500">{{ request.session.get('admin_username') or 'админ' }}</span>
|
<span class="py-1 text-slate-500">{{ request.session.get('admin_username') or 'админ' }}</span>
|
||||||
<form method="post" action="/logout">
|
<form method="post" action="/logout" class="py-1">
|
||||||
<input type="hidden" name="csrf_token" value="{{ request.session.get('csrf', '') }}" />
|
<input type="hidden" name="csrf_token" value="{{ request.session.get('csrf', '') }}" />
|
||||||
<button type="submit" class="text-slate-600 hover:text-slate-900">Выйти</button>
|
<button type="submit" class="text-slate-600 hover:text-slate-900">Выйти</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="text-slate-500">админка · v{{ version }}</span>
|
<span class="py-1 text-slate-500">админка · v{{ version }}</span>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="mx-auto max-w-5xl px-4 py-8">
|
<main class="mx-auto max-w-5xl px-4 py-8">
|
||||||
|
|
@ -39,6 +52,16 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
var navToggle = document.getElementById("nav-toggle");
|
||||||
|
var navMenu = document.getElementById("nav-menu");
|
||||||
|
if (navToggle && navMenu) {
|
||||||
|
navToggle.addEventListener("click", function () {
|
||||||
|
var hidden = navMenu.classList.toggle("hidden");
|
||||||
|
navMenu.classList.toggle("flex", !hidden);
|
||||||
|
navToggle.setAttribute("aria-expanded", String(!hidden));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var tags = {
|
var tags = {
|
||||||
b: ["<b>", "</b>"],
|
b: ["<b>", "</b>"],
|
||||||
i: ["<i>", "</i>"],
|
i: ["<i>", "</i>"],
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,8 @@
|
||||||
<th class="px-4 py-3 font-medium">Время</th>
|
<th class="px-4 py-3 font-medium">Время</th>
|
||||||
<th class="px-4 py-3 font-medium">Событие</th>
|
<th class="px-4 py-3 font-medium">Событие</th>
|
||||||
<th class="px-4 py-3 font-medium">Пользователь</th>
|
<th class="px-4 py-3 font-medium">Пользователь</th>
|
||||||
<th class="px-4 py-3 font-medium">Чат</th>
|
<th class="hidden px-4 py-3 font-medium lg:table-cell">Чат</th>
|
||||||
<th class="px-4 py-3 font-medium">Версия</th>
|
<th class="hidden px-4 py-3 font-medium xl:table-cell">Версия</th>
|
||||||
<th class="px-4 py-3 font-medium">Payload</th>
|
<th class="px-4 py-3 font-medium">Payload</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -93,8 +93,8 @@
|
||||||
<span class="text-slate-400">—</span>
|
<span class="text-slate-400">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 font-mono text-xs text-slate-500">{{ event.chat_id or "—" }}</td>
|
<td class="hidden px-4 py-3 font-mono text-xs text-slate-500 lg:table-cell">{{ event.chat_id or "—" }}</td>
|
||||||
<td class="px-4 py-3 text-xs text-slate-500">{{ event.bot_version }}</td>
|
<td class="hidden px-4 py-3 text-xs text-slate-500 xl:table-cell">{{ event.bot_version }}</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
{% if event.payload_pretty %}
|
{% if event.payload_pretty %}
|
||||||
<details>
|
<details>
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 font-medium">Ключ</th>
|
<th class="px-4 py-3 font-medium">Ключ</th>
|
||||||
<th class="px-4 py-3 font-medium">Описание</th>
|
<th class="px-4 py-3 font-medium">Описание</th>
|
||||||
<th class="px-4 py-3 font-medium">Длина</th>
|
<th class="hidden px-4 py-3 font-medium sm:table-cell">Длина</th>
|
||||||
<th class="px-4 py-3 font-medium">Изменён</th>
|
<th class="hidden px-4 py-3 font-medium md:table-cell">Изменён</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-slate-100">
|
<tbody class="divide-y divide-slate-100">
|
||||||
|
|
@ -40,8 +40,8 @@
|
||||||
<a href="/messages/{{ template.key }}" class="font-mono text-sky-700 hover:underline">{{ template.key }}</a>
|
<a href="/messages/{{ template.key }}" class="font-mono text-sky-700 hover:underline">{{ template.key }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-slate-700">{{ template.description }}</td>
|
<td class="px-4 py-3 text-slate-700">{{ template.description }}</td>
|
||||||
<td class="px-4 py-3 text-slate-500">{{ template.text_length }}</td>
|
<td class="hidden px-4 py-3 text-slate-500 sm:table-cell">{{ template.text_length }}</td>
|
||||||
<td class="px-4 py-3 text-slate-500">{{ template.updated_at.strftime("%d.%m.%Y %H:%M") }}</td>
|
<td class="hidden px-4 py-3 text-slate-500 md:table-cell">{{ template.updated_at.strftime("%d.%m.%Y %H:%M") }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@
|
||||||
<thead class="bg-slate-50 text-left text-slate-600">
|
<thead class="bg-slate-50 text-left text-slate-600">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 font-medium">Время</th>
|
<th class="px-4 py-3 font-medium">Время</th>
|
||||||
<th class="px-4 py-3 font-medium">Дни</th>
|
<th class="hidden px-4 py-3 font-medium sm:table-cell">Дни</th>
|
||||||
<th class="px-4 py-3 font-medium">Шаблон</th>
|
<th class="px-4 py-3 font-medium">Шаблон</th>
|
||||||
<th class="px-4 py-3 font-medium">Таймзона</th>
|
<th class="hidden px-4 py-3 font-medium lg:table-cell">Таймзона</th>
|
||||||
<th class="px-4 py-3 font-medium">Статус</th>
|
<th class="px-4 py-3 font-medium">Статус</th>
|
||||||
<th class="px-4 py-3 font-medium"></th>
|
<th class="px-4 py-3 font-medium"></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -31,12 +31,12 @@
|
||||||
{% for schedule in schedules %}
|
{% for schedule in schedules %}
|
||||||
<tr class="hover:bg-slate-50">
|
<tr class="hover:bg-slate-50">
|
||||||
<td class="px-4 py-3 font-mono">{{ "%02d:%02d" | format(schedule.hour, schedule.minute) }}</td>
|
<td class="px-4 py-3 font-mono">{{ "%02d:%02d" | format(schedule.hour, schedule.minute) }}</td>
|
||||||
<td class="px-4 py-3">{{ format_weekdays(schedule.weekdays) }}</td>
|
<td class="hidden px-4 py-3 sm:table-cell">{{ format_weekdays(schedule.weekdays) }}</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<div class="font-mono text-sky-700">{{ schedule.template_key }}</div>
|
<div class="font-mono text-sky-700">{{ schedule.template_key }}</div>
|
||||||
<div class="text-xs text-slate-500">{{ schedule.description or schedule.template_description }}</div>
|
<div class="text-xs text-slate-500">{{ schedule.description or schedule.template_description }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-slate-600">{{ schedule.timezone }}</td>
|
<td class="hidden px-4 py-3 text-slate-600 lg:table-cell">{{ schedule.timezone }}</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
{% if schedule.enabled %}
|
{% if schedule.enabled %}
|
||||||
<span class="rounded bg-emerald-100 px-2 py-0.5 text-xs text-emerald-800">включено</span>
|
<span class="rounded bg-emerald-100 px-2 py-0.5 text-xs text-emerald-800">включено</span>
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,9 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 font-medium">Ник</th>
|
<th class="px-4 py-3 font-medium">Ник</th>
|
||||||
<th class="px-4 py-3 font-medium">Telegram</th>
|
<th class="px-4 py-3 font-medium">Telegram</th>
|
||||||
<th class="px-4 py-3 font-medium">Уровень</th>
|
<th class="hidden px-4 py-3 font-medium sm:table-cell">Уровень</th>
|
||||||
<th class="px-4 py-3 font-medium">Баланс</th>
|
<th class="px-4 py-3 font-medium">Баланс</th>
|
||||||
<th class="px-4 py-3 font-medium">Сообщения</th>
|
<th class="hidden px-4 py-3 font-medium md:table-cell">Сообщения</th>
|
||||||
<th class="px-4 py-3 font-medium">Подписка</th>
|
<th class="px-4 py-3 font-medium">Подписка</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -54,9 +54,9 @@
|
||||||
{% if user.username %}<div>@{{ user.username }}</div>{% endif %}
|
{% if user.username %}<div>@{{ user.username }}</div>{% endif %}
|
||||||
<div class="font-mono text-xs text-slate-500">{{ user.id_user }}</div>
|
<div class="font-mono text-xs text-slate-500">{{ user.id_user }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3">{{ user.lvl }}</td>
|
<td class="hidden px-4 py-3 sm:table-cell">{{ user.lvl }}</td>
|
||||||
<td class="px-4 py-3">{{ user.balance }}</td>
|
<td class="px-4 py-3">{{ user.balance }}</td>
|
||||||
<td class="px-4 py-3">{{ user.msg_count }}</td>
|
<td class="hidden px-4 py-3 md:table-cell">{{ user.msg_count }}</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
{% if user.subscribed %}
|
{% if user.subscribed %}
|
||||||
<span class="rounded bg-emerald-100 px-2 py-0.5 text-xs text-emerald-800">да</span>
|
<span class="rounded bg-emerald-100 px-2 py-0.5 text-xs text-emerald-800">да</span>
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
__version__ = "0.5.2"
|
__version__ = "0.5.3"
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ from common import __version__
|
||||||
|
|
||||||
def test_version_is_set():
|
def test_version_is_set():
|
||||||
assert __version__
|
assert __version__
|
||||||
assert __version__ == "0.5.2"
|
assert __version__ == "0.5.3"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue