onRoute('admin.users')}
label="пользователи" metric={activeUsers} suffix="активных"
footer={`+ ${invitedUsers} в ожидании`}
help={{
title: 'Активные пользователи',
what: 'Учётные записи со статусом active и непросроченным токеном.',
why: 'Если число резко растёт — проверьте журнал invites: не утекли ли ссылки. Если падает — проверьте expired tokens и продлите тем, кто работает.',
}} />
onRoute('admin.pipeline')}
label="pipeline" metric={cronStatus === 'running' ? 'идёт' : 'ожидает'}
suffix={cronStatus === 'running' ? null : 'cron включён'}
footer={`последний прогон ${fmtAgo2(A.pipeline.last_run_at)} · ${A.pipeline.last_run_count} новых`}
statusDot={cronStatus === 'running' ? 'running' : 'idle'}
help={{
title: 'Pipeline статус',
what: 'Состояние cron-задачи, которая собирает новые стартапы из источников.',
why: 'Если pipeline остановился, корпус устаревает каждые 6 часов. Проверьте источники на этой странице и нажмите «Запустить вручную», если нужен немедленный обновитель.',
}} />
{isSuperAdmin && (
onRoute('admin.cost')}
label="расходы" metric={`$${billingSummary.monthCostUsd.toFixed(2)}`}
suffix={`/ $${A.settings.budget_monthly_usd}`}
footer={`${Math.round(billingSummary.monthCostUsd / A.settings.budget_monthly_usd * 100)}% от бюджета`}
help={{
title: 'Расходы API за месяц',
what: 'Реальная стоимость LLM-вызовов в долларах. Конверсия в рубли по ЦБ курсу.',
why: 'Если меньше 50% — бюджет можно урезать или провести больше Phase 4.5. Если выше 80% — настройте Budget guard в settings, чтобы cron не съел остаток.',
}} />
)}
onRoute('admin.audit')}
label="события за 7 дней" metric={A.auditLog.length} suffix="записей"
footer="все мутирующие действия логируются"
help={{
title: 'Журнал событий',
what: 'Запись каждого мутирующего действия пользователей: входы, инвайты, изменения настроек, запуски pipeline.',
why: 'Это ваша линия обороны. Если что-то пошло не так — здесь видно, кто и когда сделал. Регулярно просматривайте за день. Никогда не отключайте.',
}} />
{/* Bottom grid */}
{/* Recent activity */}
последняя активность
{recentEvents.map((ev, i) => )}
{/* Expiring tokens */}
требуют внимания
{expiringTokens.length === 0 ? (
Все токены валидны. В ближайшие недели регенерация не нужна.