У Квантика не было фиче-флага — его нельзя было выключить, и он всегда висел
в сайдбаре (даже у учеников без класса). Добавлено по образцу остальных игр:
- adminController.updateFeatures: 'quantik' в whitelist (PATCH принимает флаг).
- games.js: пункт «Квантик: Законы Мира» в GAME_FEATURES и FS_FEATURES
(тумблер в админке → Игры; пишет feature_quantik_enabled).
- api.js hideDisabledFeatures: quantik -> ['/quantik','/quantik.html'] (скрытие
из сайдбара при выключении) + '/quantik' в classOnlyHrefs/classOnlyPaths
(скрыт у учеников без класса, как прочие игры).
Миграция не нужна: флаг «неявно включён», пока админ не выключит (features[key]
!== false => включено). Требует Ctrl+F5 (фронт).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Не было UI для управления exam_tracks.enabled (только флаг в БД, ставился
миграцией). Добавлена админ-секция «Экзамен-модули»:
- backend exam-prep.js: GET /admin/tracks (все треки, вкл. выключенные, + число
заданий) и PATCH /admin/track (exam_key, enabled), обе requireRole('admin').
Пути без :examKey, чтобы не задеть гейт content_access.
- frontend: секция sections/exams.js (список треков + переключатель enabled),
вкладка в admin.html (admin-only через ADMIN_ONLY_TABS, locked для не-админов),
регистрация в admin.js (ROUTE_TO_SECTION).
Выключенный трек скрыт у учеников и пропадает из каталога прав доступа (тот
берёт exam_tracks WHERE enabled=1). Доступ ученикам по-прежнему в «Доступ · контент».
Требует перезапуска бэкенда + Ctrl+F5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- server: requireFeature('imggen') на /api/imggen (глобальный гейт).
- imggenController: enforcement через isFeatureEnabledForUser в status()/generate()
— учитывает глобальный флаг + оверлей класса + free_student (403 если выкл.).
- admin «games/features» + free-student: тумблер «Генерация картинок (ИИ)».
- classes.html: переключатель модуля imggen в настройках класса (per-class).
Дефолт — ON (opt-in disable), как у остальных фич. Проверено на features-движке.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Главный выключатель в разделе «Генерация картинок» (флаг on в конфиге,
независим от наличия токена). Выключено → /api/imggen отдаёт 503
«временно выключена». Админ-тест работает и при выключенном тумблере
(generateImage проверяет только наличие конфига). Бейдж различает
«Включена / Выключена / Не настроена».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Новый админ-раздел: Account ID / токен (маскируется) / модель Cloudflare,
лимиты (пауза, дневной лимит) из БД, статистика, кнопка теста генерации.
imggenController: лимиты и модель теперь из конфига, поддержка JSON и
бинарного ответа CF, переиспользуемые generateImage() и stats().
Бэкенд GET/PUT /api/admin/imggen + POST /api/admin/imggen/test (admin-only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Производный профиль (без LLM): слабые предметы, трудные темы экзамена,
цель/дата, серия — из test_sessions/exam_attempts/exam_user_plan. Подмешивается
в системный промпт → персональные ответы; такие не кэшируются глобально.
Заметки: таблица assistant_memory + фоновый LLM-экстрактор (дросселирован),
дедуп + лимит 15. Панель ученика «Что я о тебе помню» (профиль + заметки,
удаление). Админ-тумблер. API GET/DELETE /assistant/memory (/:id под
authMiddleware, владелец проверяется в хендлере).
Заодно: сверка стабильного baseline route-auth 56→66 (долг от branch-merge,
хук не идёт на merge) — новых незащищённых маршрутов не добавлено.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Новый GET /admin/assistant/models: тянет список моделей провайдера с лимитами
(OpenAI-совместимый /models: context_length+max_completion_tokens+pricing;
нативный Google generativelanguage: inputTokenLimit/outputTokenLimit) и кэширует
лимиты текущей модели на провайдере. Карточка показывает лимиты у ВСЕХ провайдеров
(не только Kilo), для отсутствующих — фоновая авто-подгрузка. В форме — кнопка
«Загрузить модели провайдера» с выбором модели и её лимитами. Так Gemini и любые
новые модели получают лимиты автоматически.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Рендер ответа: display-формулы KaTeX прокручиваются по горизонтали
(overflow-x:auto), пузырь ассистента во всю ширину, панель шире (380px) —
длинные выражения больше не режутся по правому краю.
Админка: к моделям Kilo добавлены ctx/out (из /models); на карточке Kilo
показывается «контекст N · ответ до M токенов · бесплатно».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Баннер «провайдеры недоступны» висел из старой записи assistant_failover.
Теперь успешный тест активного провайдера и смена активного снимают флаг,
плюс кнопка «Снять» на баннере (PUT /assistant {dismissFailover}).
Тест провайдера: system-инструкция + 64 токена + fallback на reasoning →
sample не показывает «мысли вслух» reasoning-моделей.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Раскрывается кнопкой «+ Добавить провайдера» (и автоматически при «Изм.»),
сворачивается после сохранения/отмены. Окно компактнее в обычном режиме.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Админ-раздел переделан: провайдеры — карточки (активный подсвечен, бейджи
ключ/активен, кнопки Сделать активным/Тест/Изменить/Удалить, hover-подъём).
Форма с лейблами и пресетами. Для Kilo — выпадающий список проверенных бесплатных
моделей (Nemotron 550B / Owl Alpha / Nemotron Nano 30B / Laguna XS) и инлайн-
переключатель модели прямо на карточке. Бэкенд: пресет Kilo + kiloModels в /admin/assistant.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
callLLMFailover пишет состояние в app_settings.assistant_failover: какой провайдер
исчерпан и каким подхвачено (или «все недоступны»); при успехе активного флаг
снимается. Админ-раздел показывает баннер «Провайдер X недоступен — работаю на Y».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Конфиг стал списком провайдеров (assistant_providers) + активный (assistant_active).
llmConfig берёт активного; providersOrdered — активный первым, затем остальные с
ключом; callLLMFailover перебирает их при 429/сетевой ошибке (второй ключ подхватывает
при исчерпании квоты). Legacy мигрируется в список. Админ-раздел: список провайдеров
(радио-активный, Тест/Изменить/Удалить) + форма с пресетами. Эндпоинты
POST/DELETE /admin/assistant/provider(/:id), POST /admin/assistant/active.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Баг «не помнит»: на самом деле free-лимит Gemini (429). callLLM теперь
возвращает ошибку; при 429 показываем «много запросов, подожди минутку —
память не потеряется» и НЕ ломаем историю (убираем неудачный вопрос); при
сбое — «не получилось, попробуй позже». Раньше показывалось «не нашёл ответ».
- В окне «Спроси» — пояснение, сколько помнит Квантик (≈6 реплик, рабочая память).
- Окна красивее: шире, аватар Квантика в шапке, мягкая анимация.
- Управление помощником вынесено в отдельный раздел админки «Помощник Квантик»
(системный вкл/выкл + модель/ключ/тест/RAG/кнопки экзамена/статистика/качество);
из раздела «Игры» конфиг убран.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Идентичность: вопросы про модель/нейросеть/провайдера/системный промпт
отбиваются шаблонно (META_RE, без вызова LLM) + запрет в системном промпте.
- Кнопки «Подсказка»/«Спросить Квантика» на карточках экзамена скрыты по
умолчанию; включаются тумблером в админке (assistant_exam_buttons →
examButtons в /context → класс html.asst-exam-on открывает кнопки).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- RAG: индексатор scripts/index-textbooks.js → textbook_chunks (миграция 063);
ask() подмешивает релевантные куски учебников (LIKE-скоринг). Покрывает
учебники со статическим текстом; JS-рендеримые — через контекст страницы.
Админка: тумблер RAG + кнопка «Переиндексировать» + число фрагментов.
- Кэш ответов (assistant_cache, 7 дней, только «чистые» вопросы без контекста/
истории) + суточный счётчик (assistant_usage: ИИ/кэш/FAQ) в админке.
- Режим учителя: роль в /context, системный промпт для учителей (задания,
план урока, учительские инструменты), подсказки-чипы для учителей.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Отдельный фича-флаг 'assistant' (вместо reuse 'pet'): админ может включать/
выключать помощника в Управление → фичи, независимо от питомца. Дефолт ON.
- FAQ расширен (~50 -> ~60): профиль/пароль, колоды/массовый импорт/SRS,
прогресс по предмету, поиск, экзамен9, питомец, «без класса», «о чём спросить».
- В «Спроси Квантика» — чипы с примерами вопросов (что можно спросить).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Обзор теперь показывает и итоги за всё время (Пользователей, Тестов пройдено, Средний
результат) и средний результат по предметам за всё время — данные грузятся из
adminGetStats параллельно с обзором. Дублей нет: Обзор был про 24ч и контент.
Убрано полностью: nav-кнопка «Статистика», панель #tab-stats, маршрут stats в
ROUTE_TO_SECTION, подключение и файл sections/stats.js. #stats-хэш падает на #overview.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Модалка индивидуальных прав пользователя (с кнопкой «врем.» — выдать право на
срок, B8) открывалась только для u.role==='teacher'. Временные/индивидуальные
права нужны и ученикам (магазин, лаба, тесты на срок). Показываем «Права» всем,
кроме admin (он и так байпасит все права).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Клиент: listRoles/createRole/updateRoleDef/deleteRole/rolePermissions. Во вкладке
«Доступ · роли» — блок «Конструктор ролей»: создать роль (имя-идентификатор +
название + базовые роли чекбоксами), список кастомных ролей, «Настроить права»
(тогглы по группам через getRolePermissions + setPermission под именем роли),
«Удалить» (возврат пользователей на базу). В списке пользователей выпадающий
список ролей теперь включает optgroup «Кастомные роли» (выбор по custom_role);
listUsers отдаёт custom_role. Phase C (произвольные роли) завершена на ветке.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Миграция 053: user_permissions.expires_at (NULL = бессрочно). Резолвер isEnabled
+ /me + /users/:id игнорируют просроченные оверрайды (наследуют роль); seedDefaults
чистит просроченные строки. setUserPermission принимает days → выдаёт право на
срок (datetime('now','+N days')). API отдаёт expiresAt. Клиент: setUserPermission(...,days).
В модалке прав пользователя — бейдж «до ДАТА» + кнопка «врем.» (выдать на N дней).
Тест: срок хранится/отдаётся, просроченное игнорируется и вычищается. Backend pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PRESETS (student): «Полный доступ», «Режим фокуса» (без магазина/испытаний),
«Ограниченный» (+ без лаборатории), «Сбросить к стандарту роли». GET
/api/permissions/presets + POST /api/permissions/class/:id/preset (admin).
Рефактор: общий applyPermsToClass() (карта key→1/0/inherit) — его используют и
bulk, и preset. В блоке «Массово по классу» — кнопки пресетов (с подтверждением).
Тест: список + применение focus/reset + валидация. Backend pass (3 baseline-Auth).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /api/permissions/class/:id/bulk { permission, enabled } (admin, явный
requireRole) — выставляет user_permissions всем ученикам класса (1/0/null=сброс),
точечный token_version bump каждому. Валидация: только студенческие ключи.
Клиент LS.setClassPermission. В админке «Доступ · роли» — блок «Массово по
классу»: выбор класса → у каждого права «включить/выключить всем / сбросить».
Тест: оверрайд всем + сброс + отклонение teacher-ключа. Backend 221 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Таблица заменена на сетку карточек, сгруппированных по типам
(Рамки/Титулы/Фоны/Эффекты) с заголовками и счётчиками. Каждая
карточка показывает настоящий вид товара:
- frame → кольцо аватара по data.css
- background → .bg-preview.bg-<slug> (тот же CSS, что у клиента)
- title → текст титула в его цвете (data.text/color)
- effect → анимация pulse / иконка-фоллбэк
Фильтр по типу, поиск и счётчик сохранены; неактивные товары
притушены; удаление компактной иконкой.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
registry: карта GROUP (Вопросы / Класс и ученики / Библиотека / Курсы и шаблоны /
Геймификация / Контент / Тесты и активность / Профиль), проброшена в byRole.group.
permissions.js: вкладка «Доступ · роли» рендерит права секциями по группам, у
каждой — «включить все / выключить все» (с подтверждением, если в группе есть
requireConfirmOff). Карточка вынесена в permCard(). Тест: definitions содержат group.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GET /api/permissions/log (admin-only) — последние изменения ролевых прав (или
?user_id= для личных оверрайдов) из admin_audit_log; читаемый текст («включил
«X» для роли «учитель»») с резолвом меток через registry. Клиент LS.permissionsLog.
Вкладка «Доступ · роли»: блок «История изменений прав ролей» с кнопкой «Показать».
Тест: admin видит записи, не-админу 403. permissions 13/13.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 4 крупные карточки статистики → компактная строка stat-пиллов
- тулбар: фильтр по типу + поиск по названию + счётчик (N из M)
- таблица: иконка-чип по типу + название с описанием в одной ячейке,
цветные бейджи типов, колонка ID убрана (id ушёл в подпись)
- состояния «Нет товаров» / «Ничего не найдено»
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
registry: поле requires (questions.delete→manage, templates.public→manage,
courses.interactive→manage, simulations.quiz→access), проброшено в byRole.
auth.requirePermission: вынесен isEnabled(); право = own AND все requires
(дочернее не работает без родителя). /me и /users/🆔 effective с учётом
requires + requires в ответе. UI permissions.js: каскад — дочернее с
невыполненной зависимостью неактивно (тумблер заблокирован + «Требует: …»).
Тест зависимости. План: plans/permissions-rework/PLAN.md. Backend 216 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Инлайн-панель формы внизу страницы заменена на модалку через LS.modal:
- shopAdminCreateItem/EditItem открывают окно openItemModal (create/edit)
- валидация: обязательное название + проверка JSON в поле «Данные»
- блокировка кнопки на время сохранения, ошибки через m.setError
- удалены инлайн-форма из admin.html и неактуальные
shopAdminSaveItem/shopAdminCancelForm/showShopForm + стейт
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Начисление монет осталось в «Пользователях» (быстрое действие quickAwardCoins)
и во вкладке «Геймификация». Из магазина удалены: HTML-блок «Начислить монеты»,
функции shopSearchUser/shopPickUser/shopAdminAwardCoins, их window-экспорты и
неиспользуемые стейт-переменные. Эндпоинт /shop/admin/award-coins не тронут —
им пользуется quickAwardCoins.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- shopAdminCreateItem/EditItem открывали форму под таблицей на 51 строку —
вне экрана, выглядело как «кнопки не работают». Добавлен showShopForm():
scrollIntoView + фокус в поле названия.
- В выпадающем списке типов «Тема» (theme) не поддерживается бэкендом
(валидация POST: frame/title/effect/background) → создание падало с 400.
Заменён на рабочий «Фон» (background); добавлена подпись в typeLabels.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
История: GET /api/access/log (admin-only) — кто/когда открыл/закрыл/сбросил
правило для контента (из admin_audit_log, имена классов/учеников резолвятся).
Клиент LS.accessLog; в режиме «По контенту» — кнопка «История изменений».
Пресет: в режиме «По классу» — «Скопировать доступ из класса [выбор]» (дополняет
текущие правила открытыми правилами класса-источника). Тест: история (admin
видит запись, учителю 403). content-access 13/13.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Панель кнопок по предметам: один клик открывает выбранному классу весь контент
этого предмета (учебники/экзамены/симуляции/курсы вместе). Нормализация поля
предмета (subject|subject_slug), метки через SUBJ_LABEL. Чистый фронтенд на
существующем accSetRule. Закрывает находку ревью «нет операции открыть весь предмет».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
select использовал var(--bg-2,#1a1a2e) — переменная не определена в светлой
теме, поэтому фон падал на тёмно-синий, а текст оставался тёмным (--text):
список сливался с фоном. Заменено на белый фон + явные цвета option.
Клик по названию контента в матрице открывает/закрывает его сразу ВСЕМ классам;
клик по имени класса (заголовок столбца) — открывает/закрывает ВЕСЬ контент этому
классу. Массовое закрытие спрашивает подтверждение; перерисовывается только tbody.
Использует существующий accSetRule (без новых эндпоинтов).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Миграция 052: мост «открыть все опубликованные курсы всем существующим классам»
(тип 'course' уже в CHECK из 051). courseController.list/search фильтруют курсы
для НЕпривилегированных по allowedRefs(uid,'course') (content_ref = courses.id как
TEXT); admin/teacher — все. /api/access/catalog отдаёт курсы; CONTENT_TYPES в
админ-UI = textbook,exam,sim,course → курсы управляются во всех режимах «Доступ».
Тест course-access 4/4 (allowlist+класс+privileged+каталог). Полный набор: 213 pass.
ВАЖНО: новый опубликованный курс по умолчанию закрыт (allowlist) — открыть классам
в админке. Мост сохранил видимость текущих опубликованных курсов у существующих
классов. class_courses остаётся для назначений с дедлайном (сверх видимости).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Бэкенд /api/access обобщён на тип 'sim': catalog отдаёт симуляции (lab_sims),
summary/matrix/class — карты по всем типам. Админ-секция «Доступ» теперь
показывает «Симуляции» во всех трёх режимах (по контенту / по классу / матрица)
+ поиск; helpers (bucket/keyName/itemsOf) обобщены через карты типов
(CONTENT_TYPES=textbook,exam,sim; course зарезервирован). Теперь админ/учитель
могут открывать/закрывать конкретные симуляции классам и ученикам — закрыт UX-
разрыв из 1a (новые классы без UI-управления). Тест: каталог включает sims; 210 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Режим «По контенту»: поиск по названию в левой колонке (обновляет только список,
фокус сохраняется) + подзаголовки по предмету (Математика/Физика/…). У раскрытого
класса рядом с tri-state каждого ученика — бейдж итогового доступа «видит/не видит
· лично|по классу|по умолч.» (считается клиентски из загруженных правил) — снимает
путаницу «наследовать/открыт/закрыт».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GET /api/access/matrix (классы + карта открытого контента одним запросом,
скоуп учителя). Клиент LS.accessMatrix. Третий режим вкладки «Доступ»:
таблица контент × классы с чекбоксами (правка в один клик) + поиск по
названию (обновляет только tbody — фокус ввода сохраняется), залипающие
заголовки. Тест /api/access смонтирован в харнесс; content-access.test 11/11
(+матрица: учитель видит свои классы и открытый контент, ученику 403).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- contentAccess.purgeAccessFor(scope,id) — единая точка очистки content_access
(нет FK). deleteClass и _deleteUserTx переведены на неё (убрано дублирование).
- Админ-UI: confirm() перед «Закрыть у всех / Закрыть весь» (необратимая массовая
операция больше не срабатывает мгновенно).
- Новый тест content-access.test.js (9/9): allowlist, ученик>класс, наследование
главой хаба, admin/teacher bypass, allowedRefs/filterTextbooks, purgeAccessFor,
чистка правил при DELETE класса. Полный backend-набор: 203/206 (3 — baseline Auth).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- gam-award-grid/gam-reset-grid: CSS Grid, адаптив 800px
- gam-user-col/filter/select — единые стили из design system
- gam-preset/gam-reason-tag — через CSS-классы, без inline
- gam-num-input: Unbounded шрифт, выровненный по центру
- gam-award-footer + gam-reset-warning как отдельные блоки
- убраны все эмоджи; пресеты сбрасываются через gamSetXP/gamSetCoins
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- select с полным списком пользователей + фильтр по имени (вместо typeahead)
- пресеты XP (0/10/25/50/100/250) и монет (0/10/25/50) с подсветкой активного
- пресеты причин (кнопки) + поле для своей причины
- fix: xp/coins теперь Number(value) без || 0 — значение 0 не начисляется
- форма сброса прогресса — тоже select из того же кэша пользователей
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Секция игнорировала флаг allow_html и всегда экранировала текст/опции/
пояснение, из-за чего <div class=task-figure><img>, <b> и пр. показывались
как сырой текст. Теперь — как в test-run.html: allow_html ? raw : esc.
Также добавлен q.allow_html в SELECT списка вопросов (его не было в ответе API).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
renderMath в _shared.js распознавал только \(…\) и \[…\], из-за чего
873 вопроса с долларовыми разделителями не рендерили формулы в админке.
$$ ставится раньше $, чтобы auto-render не принял его за два пустых $.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
adminController.getHealth: активные health-проверки — отклик БД (ping, мс) и
тест записи на диск рядом с БД; вердикт уходит в critical при недоступной БД
или диске, warning при медленном отклике БД (>100мс). Плюс recentErrorList —
последние 8 записей error_log (level/route/method/message/время).
admin.js: панель «Диагностика» — индикаторы БД/диска (зелёный/красный) +
лента последних ошибок с цветом по уровню.
Проверено: checks {dbOk,dbPingMs,diskWritable}, список ошибок отдаётся.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
metrics.js: сэмплинг раз в минуту в кольцевой буфер (cap 24ч, unref) —
ts/rss/heapUsed/reqPerMin/reqDelta/err5xx/p95; history() + поле history в
snapshot (последние 180 точек).
admin.js: секция «Тренды» с 4 мини-графиками (canvas): Память RSS, Запросы/мин,
Ошибки 5xx, Латентность p95 — линия + заливка + подписи макс/последнее.
Обновляются вместе с live-рефрешем.
Проверено: сэмплер пишет, история в snapshot, графики рисуются (на старте —
«накопление данных…», далее наполняются).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
backend/src/utils/metrics.js: лёгкие in-memory метрики (сброс при рестарте) —
всего запросов, req/min (скользящее окно), латентность avg/p50/p95/p99,
разбивка по статусам 2xx/3xx/4xx/5xx, топ маршрутов по частоте/латентности/
ошибкам (группировка по шаблону route.path, не по URL).
server.js: middleware (на /api, по res 'finish') пишет латентность и статус.
adminController.getMetrics + GET /api/admin/metrics (под admin-auth).
admin.js: health-страница переведена на refreshHealth/renderHealth (Level 1)
+ секция «Метрики запросов»: карточки req/min/всего/avg/p95/p99/5xx, цветная
полоса статусов, топ медленных/частых/ошибочных маршрутов.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>