feat: страница логов /ui/logs с SSE real-time потоком
Some checks failed
CI / test (push) Successful in 39s
Deploy / deploy (push) Failing after 15s

- log_buffer: RingBufferHandler, кольцевой буфер 600 записей, fan-out SSE
- ui_logs: GET /ui/logs (HTML), GET /ui/logs/stream (EventSource)
- main: install_log_handler при старте, подключён router логов
- nav_rail: ссылка Логи, root_html: кнопка-ссылка Логи
- Исправлено: NaN/Inf/NUL в теле вебхука → 500 от PostgreSQL jsonb
- Тесты: test_log_buffer, test_json_sanitize; 51 passed

Made-with: Cursor
This commit is contained in:
Alexandr
2026-04-03 15:56:58 +03:00
parent 18ba48e8d0
commit 80645713a0
12 changed files with 465 additions and 12 deletions

View File

@ -0,0 +1,26 @@
"""Подготовка структур из JSON к записи в PostgreSQL jsonb."""
from __future__ import annotations
import math
from typing import Any
def sanitize_for_jsonb(obj: Any) -> Any:
"""
- float NaN / ±Inf → None (иначе json.dumps даёт невалидный JSON для PG / сюрпризы при записи).
- Символ NUL в строках убрать (PostgreSQL text/jsonb NUL в строке не принимает).
"""
if isinstance(obj, float):
if math.isnan(obj) or math.isinf(obj):
return None
return obj
if isinstance(obj, str):
if "\x00" not in obj:
return obj
return obj.replace("\x00", "")
if isinstance(obj, dict):
return {k: sanitize_for_jsonb(v) for k, v in obj.items()}
if isinstance(obj, list):
return [sanitize_for_jsonb(x) for x in obj]
return obj