feat(notifications): центр уведомлений о системных событиях
Добавлена система уведомлений о системных и фоновых событиях (новые записи журнала, обработка фото, авто-проверка текста, блокировки IP, бэкапы). - backend (server.js, worker.js): - каталог NOTIFY_TYPES с метаданными и уровнями - таблицы notifications и notification_reads в db/init.sql и db/migration.sql - SSE-стрим GET /api/notifications/stream через Redis pub/sub с in-memory fallback - REST API: список, счётчик непрочитанных, отметка о прочтении, удаление, очистка - настройки уведомлений в settings (notify_enabled, notify_retention_days, notify_<тип>) - автоматическая очистка старых уведомлений по расписанию - frontend: - колокольчик со счётчиком непрочитанных в шапке (admin.js) - страница списка уведомлений public/notifications.html и public/js/notifications.js - секция настроек уведомлений в public/settings.html и public/js/settings.js - стили для уведомлений в public/admin.css - тесты и документация: - добавлены проверки в api.smoketest.js - обновлены README.md и AGENTS.md
This commit is contained in:
@@ -568,4 +568,49 @@ body[data-page=settings] .page-head{align-items:flex-end}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce){.save-bar{animation:none}.toggle-track,.toggle-thumb{transition:none}.icon-btn.spinning svg{animation:none}}
|
||||
|
||||
/* --- Notifications --- */
|
||||
.sidebar nav a[data-nav=notifications] svg{color:#f59e0b}
|
||||
.notify-bell{position:relative}
|
||||
.notify-badge{position:absolute;left:20px;top:-3px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:#ef4444;color:#fff;font-size:.62rem;font-weight:700;line-height:16px;text-align:center}
|
||||
.notify-badge[hidden]{display:none}
|
||||
.notify-panel{position:fixed;left:12px;bottom:96px;width:min(380px,calc(100vw - 24px));max-height:min(70vh,520px);display:flex;flex-direction:column;background:var(--card);border:1px solid var(--border);border-radius:14px;box-shadow:0 18px 40px rgba(0,0,0,.18);z-index:90;overflow:hidden}
|
||||
.notify-panel[hidden]{display:none}
|
||||
.notify-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 14px;border-bottom:1px solid var(--border);font-size:.9rem}
|
||||
.notify-list{overflow-y:auto;flex:1}
|
||||
.notify-empty{padding:22px 14px;color:var(--muted);font-size:.85rem;text-align:center}
|
||||
.notify-item{display:flex;gap:10px;padding:11px 14px;border-bottom:1px solid var(--border);text-decoration:none;color:var(--text);transition:background .15s}
|
||||
.notify-item:hover{background:var(--bg)}
|
||||
.notify-item:last-child{border-bottom:none}
|
||||
.notify-item.unread{background:rgba(37,99,235,.05)}
|
||||
.notify-item.unread .notify-title{font-weight:600}
|
||||
.notify-item.level-warning .notify-ic{background:rgba(245,158,11,.14);color:#f59e0b}
|
||||
.notify-item.level-critical .notify-ic{background:rgba(239,68,68,.14);color:#ef4444}
|
||||
.notify-ic{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:8px;background:var(--bg);color:var(--muted);flex-shrink:0}
|
||||
.notify-ic svg{width:15px;height:15px}
|
||||
.notify-body{display:flex;flex-direction:column;gap:2px;min-width:0}
|
||||
.notify-title{font-size:.84rem;line-height:1.3}
|
||||
.notify-text{font-size:.76rem;color:var(--muted);line-height:1.35;overflow-wrap:anywhere}
|
||||
.notify-time{font-size:.7rem;color:var(--muted)}
|
||||
.notify-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 14px;border-top:1px solid var(--border);font-size:.78rem}
|
||||
.notify-foot a{color:var(--accent);text-decoration:none}
|
||||
.notify-foot a:hover{text-decoration:underline}
|
||||
.notify-link{background:none;border:none;color:var(--accent);font-size:.78rem;cursor:pointer;padding:0}
|
||||
.notify-link:hover{text-decoration:underline}
|
||||
.notify-types{display:flex;flex-direction:column;gap:6px;margin-top:6px}
|
||||
.notify-type-badge{font-size:.65rem;font-weight:700;color:var(--muted);background:var(--bg);border:1px solid var(--border);border-radius:999px;padding:1px 7px;margin-left:6px;white-space:nowrap}
|
||||
.notify-page-head{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:14px}
|
||||
.notify-page-head .grow{flex:1}
|
||||
.notify-page-list{display:flex;flex-direction:column;gap:8px}
|
||||
.notify-card{display:flex;gap:12px;padding:14px 16px;background:var(--card);border:1px solid var(--border);border-radius:12px}
|
||||
.notify-card.unread{border-color:rgba(37,99,235,.45);background:rgba(37,99,235,.04)}
|
||||
.notify-card.level-warning{border-left:3px solid #f59e0b}
|
||||
.notify-card.level-critical{border-left:3px solid #ef4444}
|
||||
.notify-card .notify-body{flex:1}
|
||||
.notify-card .notify-title{font-size:.92rem}
|
||||
.notify-card .notify-text{font-size:.82rem}
|
||||
.notify-card-actions{display:flex;flex-direction:column;gap:6px;align-items:flex-end}
|
||||
@media(max-width:1024px){
|
||||
.notify-panel{left:12px;right:12px;width:auto;bottom:12px}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user