feat: log bot command invocations to the event stream

Bot commands were the only user-initiated path that didn't surface in
the dashboard. They now produce ``command_handled`` /
``command_rate_limited`` / ``command_failed`` rows in ``EventLog``
alongside tracker and action events.

Backend
- ``EventLog`` gains nullable ``command_tracker_id`` / ``telegram_bot_id``
  FKs plus deletion-snapshot name columns (idempotent migration).
- New ``_log_command_event`` helper emits one row per invocation at the
  three branches in ``handle_command``. Logging failures are swallowed
  so they cannot block the user-visible reply.
- Telegram ``from`` is captured in poller and webhook, whitelisted to
  identity fields by ``_normalize_issuer`` (drops ``language_code`` and
  any future PII), persisted under ``details.issuer``.
- ``/api/status`` resolves live ``CommandTracker`` / ``TelegramBot``
  names (mirroring the action pattern) and exposes ``tracker_id``,
  ``command_tracker_id``, ``telegram_bot_id`` so the frontend can
  deep-link.

Frontend
- Event rows are now clickable and open a detail modal with full
  provenance (bot → chat → issuer → provider), raw ``details`` JSON,
  and per-entity action buttons.
- Buttons use the existing ``requestHighlight`` + ``goto`` crosslink
  pattern, so clicking lands on the entity's list page with that
  specific card scrolled into view and pulsing.
- Auto-refresh dropdown (Off / 10s / 30s / 1m / 5m) persisted in
  ``localStorage``; ticker pauses while the tab is hidden.
- Event-type filter, dashboard verb labels, and gradients extended for
  the three new ``command_*`` types.
- Filled in pre-existing missing i18n keys (``common.hide`` /
  ``common.show`` / ``commandConfig.noCommandsForProvider``).

Tests
- New ``test_command_event_logging.py`` covers subject formatting,
  issuer normalization, the three event branches, and graceful failure
  when the DB is unreachable. ``pytest packages/server/tests/`` → 96/96.
This commit is contained in:
2026-05-07 22:22:41 +03:00
parent 632e4c1aa3
commit 35a3008896
13 changed files with 952 additions and 50 deletions
+40
View File
@@ -87,6 +87,15 @@
"actionSuccess": "действие выполнено",
"actionPartial": "действие частично",
"actionFailed": "действие провалено",
"commandHandled": "команда обработана",
"commandRateLimited": "ограничение частоты",
"commandFailed": "команда упала",
"autoRefreshTitle": "Интервал авто-обновления списка событий",
"refreshOff": "Выкл",
"refresh10s": "10с",
"refresh30s": "30с",
"refresh60s": "1м",
"refresh5m": "5м",
"searchEvents": "Поиск событий...",
"allEvents": "Все события",
"filterAssetsAdded": "Добавление файлов",
@@ -97,6 +106,9 @@
"filterActionSuccess": "Действие выполнено",
"filterActionPartial": "Действие частично",
"filterActionFailed": "Действие провалено",
"filterCommandHandled": "Команда обработана",
"filterCommandRateLimited": "Ограничение частоты",
"filterCommandFailed": "Команда упала",
"allProviders": "Все провайдеры",
"newestFirst": "Сначала новые",
"oldestFirst": "Сначала старые",
@@ -141,6 +153,23 @@
"newTracker": "Новый трекер",
"eventsTotal": "Событий"
},
"events": {
"detailTitle": "Детали события",
"bot": "Бот",
"chat": "Чат",
"issuer": "Отправитель",
"commandTracker": "Командный трекер",
"tracker": "Трекер",
"action": "Действие",
"provider": "Провайдер",
"assetsCount": "Файлов",
"openProvider": "Открыть провайдера",
"openBot": "Открыть бота",
"openCommandTracker": "Открыть командный трекер",
"openAction": "Открыть действие",
"openTracker": "Открыть трекер",
"rawDetails": "Сырые данные"
},
"providers": {
"title": "Сервисные",
"titleEmphasis": "провайдеры",
@@ -889,6 +918,7 @@
"titleEmphasis": "конфигурации",
"countLabel": "конфигураций",
"title": "Конфигурации команд",
"noCommandsForProvider": "Для этого типа провайдера нет доступных команд.",
"description": "Настройки команд для взаимодействия с Telegram-ботами",
"newConfig": "Новая конфигурация",
"name": "Название",
@@ -1025,6 +1055,8 @@
"edit": "Редактировать",
"description": "Описание",
"close": "Закрыть",
"hide": "Скрыть",
"show": "Показать",
"confirm": "Подтвердить",
"cannotDelete": "Невозможно удалить",
"blockedByIntro": "На объект ссылаются:",
@@ -1149,6 +1181,14 @@
"actionSuccess": "Запланированное действие выполнено",
"actionPartial": "Запланированное действие выполнено частично",
"actionFailed": "Запланированное действие провалено",
"commandHandled": "Команда бота обработана",
"commandRateLimited": "Команда бота ограничена по частоте",
"commandFailed": "Команда бота вызвала ошибку",
"refreshOff": "Автообновление выключено",
"refresh10s": "Обновлять каждые 10 секунд",
"refresh30s": "Обновлять каждые 30 секунд",
"refresh60s": "Обновлять каждую минуту",
"refresh5m": "Обновлять каждые 5 минут",
"newestFirst": "Сначала новые события",
"oldestFirst": "Сначала старые события",
"chatActionNone": "Индикатор не показывается",