first commit
This commit is contained in:
346
lastochka-ui/LOGS.md
Normal file
346
lastochka-ui/LOGS.md
Normal file
@@ -0,0 +1,346 @@
|
||||
# Логирование в Ласточке
|
||||
|
||||
## Обзор
|
||||
|
||||
Система логирования записывает все важные действия пользователей и администраторов для:
|
||||
|
||||
- 🔍 Аудита безопасности
|
||||
- 📊 Анализа использования
|
||||
- 🐛 Отладки проблем
|
||||
- 📋 Соответствия требованиям
|
||||
|
||||
## Типы действий
|
||||
|
||||
### Пользовательские
|
||||
|
||||
| Действие | Код | Описание |
|
||||
|----------|-----|----------|
|
||||
| Вход | `login` | Пользователь вошёл в систему |
|
||||
| Выход | `logout` | Пользователь вышел из системы |
|
||||
| Регистрация | `register` | Новый пользователь зарегистрировался |
|
||||
| Обновление профиля | `update_profile` | Изменены данные профиля |
|
||||
| Смена пароля | `change_password` | Пользователь сменил пароль |
|
||||
|
||||
### Группы и каналы
|
||||
|
||||
| Действие | Код | Описание |
|
||||
|----------|-----|----------|
|
||||
| Создание группы | `create_group` | Создана новая группа/канал |
|
||||
| Удаление группы | `delete_group` | Группа удалена |
|
||||
|
||||
### Административные
|
||||
|
||||
| Действие | Код | Описание |
|
||||
|----------|-----|----------|
|
||||
| Блокировка | `ban_user` | Пользователь заблокирован |
|
||||
| Разблокировка | `unban_user` | Пользователь разблокирован |
|
||||
| Удаление пользователя | `delete_user` | Аккаунт удалён |
|
||||
| Изменение настроек | `update_settings` | Изменены настройки системы |
|
||||
| Отправка уведомления | `send_notification` | Массовая рассылка |
|
||||
| Экспорт данных | `export_data` | Выгрузка данных |
|
||||
|
||||
## Структура лога
|
||||
|
||||
```typescript
|
||||
interface ActivityLog {
|
||||
id: string // Уникальный ID записи
|
||||
userId: string // ID пользователя
|
||||
userName: string // Отображаемое имя
|
||||
action: ActionType // Тип действия
|
||||
target: string // Цель (user/group/system)
|
||||
targetId?: string // ID цели (если есть)
|
||||
details?: string // Дополнительные детали
|
||||
ip: string // IP адрес
|
||||
timestamp: Date // Время действия
|
||||
}
|
||||
```
|
||||
|
||||
## Примеры записей
|
||||
|
||||
### Вход пользователя
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "log_1234567890",
|
||||
"userId": "user_abc123",
|
||||
"userName": "Иван Петров",
|
||||
"action": "login",
|
||||
"target": "system",
|
||||
"ip": "192.168.1.100",
|
||||
"timestamp": "2026-03-17T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Создание группы
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "log_1234567891",
|
||||
"userId": "user_abc123",
|
||||
"userName": "Иван Петров",
|
||||
"action": "create_group",
|
||||
"target": "group",
|
||||
"targetId": "grp_xyz789",
|
||||
"details": "Создана группа \"Рабочая\"",
|
||||
"ip": "192.168.1.100",
|
||||
"timestamp": "2026-03-17T11:45:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Блокировка пользователя (админ)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "log_1234567892",
|
||||
"userId": "admin_001",
|
||||
"userName": "Администратор",
|
||||
"action": "ban_user",
|
||||
"target": "user",
|
||||
"targetId": "user_def456",
|
||||
"details": "Нарушение правил сообщества",
|
||||
"ip": "10.0.0.1",
|
||||
"timestamp": "2026-03-17T14:20:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Страница логов
|
||||
|
||||
### Фильтры
|
||||
|
||||
**Поиск:**
|
||||
- По имени пользователя
|
||||
- По типу действия
|
||||
- По цели действия
|
||||
|
||||
**Фильтры:**
|
||||
- Тип действия (все/конкретный)
|
||||
- Пользователь (все/конкретный)
|
||||
- Период дат (с/по)
|
||||
|
||||
**Сортировка:**
|
||||
- По времени (возрастанию/убыванию)
|
||||
- По типу действия
|
||||
|
||||
**Пагинация:**
|
||||
- 25 / 50 / 100 / 200 записей на странице
|
||||
|
||||
### Экспорт
|
||||
|
||||
```typescript
|
||||
// Экспорт в JSON
|
||||
const handleExport = () => {
|
||||
const data = JSON.stringify(filteredLogs, null, 2)
|
||||
const blob = new Blob([data], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `logs-${new Date().toISOString().split('T')[0]}.json`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Эндпоинты
|
||||
|
||||
```
|
||||
GET /api/admin/logs
|
||||
?action=login # Фильтр по действию
|
||||
&userId=user123 # Фильтр по пользователю
|
||||
&from=2026-03-01 # С даты
|
||||
&to=2026-03-17 # По дату
|
||||
&sort=timestamp # Сортировка
|
||||
&order=desc # Порядок
|
||||
&page=1 # Страница
|
||||
&limit=50 # Лимит
|
||||
|
||||
DELETE /api/admin/logs # Очистка логов
|
||||
```
|
||||
|
||||
### Серверная логика
|
||||
|
||||
```typescript
|
||||
// Middleware для логирования действий
|
||||
async function logAction(
|
||||
userId: string,
|
||||
action: ActionType,
|
||||
target: string,
|
||||
details?: string
|
||||
) {
|
||||
const log: ActivityLog = {
|
||||
id: generateId(),
|
||||
userId,
|
||||
userName: await getUserName(userId),
|
||||
action,
|
||||
target,
|
||||
details,
|
||||
ip: getRequestIP(),
|
||||
timestamp: new Date(),
|
||||
}
|
||||
|
||||
await db.logs.insert(log)
|
||||
|
||||
// Асинхронная отправка в аналитику
|
||||
sendToAnalytics(log)
|
||||
}
|
||||
```
|
||||
|
||||
## Хранение
|
||||
|
||||
### Стратегия
|
||||
|
||||
```typescript
|
||||
// Автоматическая очистка старых логов
|
||||
const RETENTION_DAYS = 90
|
||||
|
||||
async function cleanupOldLogs() {
|
||||
const cutoffDate = new Date()
|
||||
cutoffDate.setDate(cutoffDate.getDate() - RETENTION_DAYS)
|
||||
|
||||
await db.logs.delete({
|
||||
timestamp: { lt: cutoffDate }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Индексы
|
||||
|
||||
```sql
|
||||
-- Для ускорения поиска
|
||||
CREATE INDEX idx_logs_timestamp ON logs(timestamp DESC);
|
||||
CREATE INDEX idx_logs_user ON logs(userId);
|
||||
CREATE INDEX idx_logs_action ON logs(action);
|
||||
CREATE INDEX idx_logs_target ON logs(target);
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
### Логирование действия
|
||||
|
||||
```typescript
|
||||
import { logAction } from '@/lib/logging'
|
||||
|
||||
// При входе
|
||||
await logAction(userId, 'login', 'system')
|
||||
|
||||
// При создании группы
|
||||
await logAction(userId, 'create_group', 'group', {
|
||||
groupId: newGroup.id,
|
||||
groupName: newGroup.name,
|
||||
})
|
||||
|
||||
// При блокировке (админ)
|
||||
await logAction(adminId, 'ban_user', 'user', {
|
||||
bannedUserId: userId,
|
||||
reason: 'Нарушение правил',
|
||||
duration: 7, // дней
|
||||
})
|
||||
```
|
||||
|
||||
### Поиск логов
|
||||
|
||||
```typescript
|
||||
import { useAdminStore } from '@/store/admin'
|
||||
|
||||
const { logs, loadLogs } = useAdminStore()
|
||||
|
||||
// Загрузка с фильтрами
|
||||
await loadLogs({
|
||||
action: 'login',
|
||||
userId: 'user123',
|
||||
from: '2026-03-01',
|
||||
to: '2026-03-17',
|
||||
limit: 100,
|
||||
})
|
||||
```
|
||||
|
||||
## Безопасность
|
||||
|
||||
### Защита логов
|
||||
|
||||
- Доступ только для администраторов
|
||||
- Логи действий администраторов тоже логируются
|
||||
- Запрет на удаление отдельных записей
|
||||
- Только массовая очистка по истечении срока
|
||||
|
||||
### Аудит
|
||||
|
||||
```typescript
|
||||
// Проверка прав доступа
|
||||
async function canAccessLogs(userId: string): Promise<boolean> {
|
||||
const user = await getUser(userId)
|
||||
return user?.role === 'admin' || user?.role === 'superadmin'
|
||||
}
|
||||
|
||||
// Логирование доступа к логам
|
||||
await logAction(adminId, 'view_logs', 'system')
|
||||
```
|
||||
|
||||
## Мониторинг
|
||||
|
||||
### Оповещения
|
||||
|
||||
```typescript
|
||||
// Подозрительная активность
|
||||
if (failedLoginAttempts > 10) {
|
||||
await sendAlert('Много неудачных попыток входа', {
|
||||
userId,
|
||||
ip,
|
||||
count: failedLoginAttempts,
|
||||
})
|
||||
}
|
||||
|
||||
// Массовые действия
|
||||
if (actionsPerMinute > 100) {
|
||||
await sendAlert('Подозрительная активность', {
|
||||
userId,
|
||||
actions: actionsPerMinute,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Метрики
|
||||
|
||||
```typescript
|
||||
// Статистика за период
|
||||
const stats = {
|
||||
totalLogs: await logs.count(),
|
||||
byAction: await logs.groupBy('action'),
|
||||
byUser: await logs.groupBy('userId'),
|
||||
peakHour: await logs.peakHour(),
|
||||
avgPerDay: await logs.averagePerDay(),
|
||||
}
|
||||
```
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### Поиск всех входов пользователя
|
||||
|
||||
```typescript
|
||||
const logins = logs.filter(log =>
|
||||
log.userId === 'user123' && log.action === 'login'
|
||||
)
|
||||
```
|
||||
|
||||
### Поиск действий за сегодня
|
||||
|
||||
```typescript
|
||||
const today = new Date().toDateString()
|
||||
const todayLogs = logs.filter(log =>
|
||||
new Date(log.timestamp).toDateString() === today
|
||||
)
|
||||
```
|
||||
|
||||
### Подсчёт действий по типу
|
||||
|
||||
```typescript
|
||||
const actionCounts = logs.reduce((acc, log) => {
|
||||
acc[log.action] = (acc[log.action] || 0) + 1
|
||||
return acc
|
||||
}, {} as Record<string, number>)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Ласточка** — народный мессенджер с открытым кодом 🕊️
|
||||
Reference in New Issue
Block a user