GET /api/admin/overview returns 24h digest (~0.08ms/call).
- adminController.getOverview: 7 prepared statements (users 24h, sessions 24h, active users, classes count, failed sessions, banned this week, top-5 sessions)
- new section frontend/js/admin/sections/overview.js (~205L): bento-grid cards, alerts (only when >0), top-5 table, quick-links
- nav-item + tab-pane reordered: #overview is now default; #stats remains routable
Auth: admin-only (inside requireRole('admin') block, sibling of /stats).
Backward compat: all 13 existing routes unchanged.
Known follow-ups (post-merge polish):
- activeClasses counts all (label could be 'Всего классов')
- failedSessions24h includes in_progress (could tighten to abandoned only)
- topSessions24h drops NULL-score completed rows
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.6 KiB
Phase 3: Dashboard #overview
Status: ✅ Implemented (pending review) Parent plan: PLAN.md Domain: fullstack Parallelizable with: Phase 4, Phase 5
Objective
Сделать #overview дефолтным route'ом admin-панели — landing-страница "что требует внимания". Заменяет нынешние обезличенные stat-cards (totalUsers, totalTests, ...) на actionable digest за последние 24-48 часов.
Tasks
- Backend: новый endpoint
GET /api/admin/overview:{ newUsers24h: number, // регистрации за 24ч newSessions24h: number, // запущенных тестов bannedThisWeek: [{ id, name, banned_at }], failedSessions24h: number, // тесты со статусом не completed topSessions24h: [{ id, user_name, subject, score, total, finished_at }], // топ-5 за 24ч activeUsers24h: number, // unique last_login за 24ч pendingMigrations: number, // если возможно проверить recentErrors: [{ id, type, message, created_at }] // если есть audit log с типом 'error' }- Контроллер:
backend/src/controllers/adminController.js→ новая функцияgetOverview - Route: добавить в
backend/src/routes/admin.js - Auth: admin или teacher (как остальные admin/* — RBAC same)
- Performance: один запрос для каждого поля, простые COUNT/SELECT, без JOIN'ов где возможно
- Контроллер:
- Frontend: новый section
frontend/js/admin/sections/overview.js:- Использует структуру из Phase 2
- Загружает
/api/admin/overview - Рендерит карточки в
<div id="tab-overview">секции:- Активность 24ч — registr, sessions, active users (число + спарклайн если есть данные)
- Требует внимания — banned this week (если >0), failed sessions (если >5%), pending migrations
- Топ-сессии — таблица top-5 за день с click→drilldown
- Quick links — "Все пользователи", "Все сессии", "Создать тест" (deep-link в соответствующие routes)
- LS.skeleton при загрузке, LS.state.error на fail
- HTML: добавить
<div class="tab-pane" id="tab-overview">в admin.html (перед остальными tab-pane) - Nav: добавить admin-nav-item для
overview(icon: layout-dashboard / activity) - Регистрация в ROUTE_TO_SECTION (из Phase 2):
overview: 'overview' - Сделать
#overviewдефолтным route'ом в router (из Phase 1) — если пустой hash, navigate to#overviewвместо#stats - Старый
#statsостаётся как доступный route (legacy backend stats), но не дефолтный
Files to Modify/Create
backend/src/controllers/adminController.js— добавитьgetOverviewфункцию (~60-90L)backend/src/routes/admin.js— добавитьrouter.get('/overview', requireAdmin, getOverview)frontend/js/admin/sections/overview.js— новый, ~250-350Lfrontend/admin.html— добавить<div id="tab-overview">+ nav-item +<script>тегfrontend/js/admin/admin.js— изменить default route на#overview(если ещё не сделано через router.js config)
Acceptance Criteria
/admin(без hash) → редирект на#overview#overviewпоказывает реальные данные (новые регистрации видны если кто-то зарегистрировался)- Карточки кликабельные (click → deep-link в users / sessions с фильтром)
- При отсутствии данных (свежая БД) — empty states, не crash
- Endpoint выполняется <100ms на тестовой БД
- F5 на
#overviewработает - Auth: только teacher/admin (как остальные /admin/*)
Notes
Что считать "требует внимания"
Делаем простые threshold'ы для MVP:
bannedThisWeek> 0 — показать жёлтую карточку с именамиfailedSessions24h> 0 — показать список (failed = status != 'completed')pendingMigrations> 0 — показать красную (но это редкий случай — миграции применяются на старте)
Можно потом расширить до "новых жалоб", "переполненных классов", и т.д. — это уже после merge.
Дизайн карточек
Bento-grid из 4-6 карточек:
+---------+---------+
| 24ч | Внимание|
| метрики | ! |
+---------+---------+
| Топ сессии |
| (table) |
+---------+---------+
| Quick links |
+-------------------+
Использовать существующие стили .stat-card, .section-title из admin.html — не изобретать.
Что НЕ делать в этой фазе
- Не делать реалтайм WebSocket-обновления (это уже Phase 7+)
- Не делать графики/чарты (пока числа + sparkline опционально)
- Не делать персонализацию (например "ваши классы")
Review Checklist
- Endpoint работает и возвращает корректную форму ответа
- Frontend handles empty state gracefully
- Click на quick-link корректно навигирует через AdminRouter
- Нет hardcoded date math (использовать SQL
datetime('now', '-24 hours')) - Roles correct (admin/teacher only, не у students)
- No SQL injection — параметры через
?placeholders - Build passes
Handoff to Next Phase
Endpoint shape (GET /api/admin/overview):
{
"newUsers24h": 0,
"newSessions24h": 0,
"activeUsers24h": 2,
"activeClasses": 5,
"failedSessions24h": 0,
"bannedThisWeek": [
{ "id": 42, "name": "...", "email": "...", "banned_at": "2026-05-15 12:30:00" }
],
"topSessions24h": [
{ "id": 101, "user_name": "...", "subject_name": "Физика",
"score": 18, "total": 20, "percent": 90.0, "finished_at": "2026-05-16 09:14:22" }
]
}
Performance: ~0.08ms/call avg (benchmarked 100 iters) — well under 100ms target.
Auth: uses router.use(requireRole('admin')) block (admin-only, same as /stats).
/features block (teacher+admin) is above the route. Teacher access NOT granted — matches
sibling /stats behavior. If Phase 4-6 wants teacher access, move /overview above the
admin-only router.use(...) line in backend/src/routes/admin.js.
Quick-link wiring (Phase 4/5 extension point):
Quick-link buttons live in overview.js → render() → .ov-quick-grid. They use a
data-go="#hash" attribute and a delegated click → AdminRouter.navigate(...). To add
a new quick-link, append a <button class="ov-quick-btn" data-go="#whatever"> to the grid.
Deep-link openings for Phase 6:
#usersand#sessionsare wired (vanilla routes — no filter params yet)- Future: extend
AdminRouterto support#users?filter=banned/#sessions?status=failedquery-style params. Currently router parsesroute + params(/users/123form), no?parsing — Phase 6 can extendrouter.js::parseHash().
Router defaults updated:
admin.js::initAdminRouter::activate()fallback route changed'stats'→'overview'- Initial dispatch (empty hash) navigates to
#overviewinstead of#stats - Initial init call:
AdminSections.overview.init()instead ofAdminSections.stats.init()
Files modified/created (line counts):
- NEW:
frontend/js/admin/sections/overview.js(~205 lines) backend/src/controllers/adminController.js(+57 lines: new statements +getOverview)backend/src/routes/admin.js(+1 line)js/api.js(+1 line helper + 1 export)frontend/admin.html(nav-item +3 lines, tab-pane +4 lines, script tag +1 line, swapactive)frontend/js/admin/admin.js(ROUTE_TO_SECTION+1 entry, default route refs swapped fromstats→overview)
Unrelated to Phase 3 (do not touch): old #stats route remains functional — used as fallback in the past, can be linked from Overview as "детальная статистика" in a future polish pass.