feat: Implement user management system in admin panel
- Added user statistics display including total users, active users today, banned users, and admin count. - Introduced filtering options for users by role and status, along with a dynamic search feature. - Created a user table with relevant information and actions for editing, banning, and deleting users. - Implemented REST API endpoints for user management, including fetching, updating, banning, and deleting users. - Enhanced server-side logging for user management actions. - Updated styles for the admin panel to improve user experience and responsiveness. - Added Docker support with a complete setup for deployment, including Apache configuration for reverse proxy and WebSocket support. - Included environment configuration and backup procedures in the documentation.
This commit is contained in:
parent
4c1aa87905
commit
81a7108758
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Node modules
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Базы данных
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Временные файлы
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# OS файлы
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Docker volumes
|
||||||
|
data/
|
||||||
|
|
||||||
|
# Временные файлы разработки
|
||||||
|
.tmp/
|
||||||
|
temp/
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Environment Configuration
|
||||||
|
# Для production используйте docker-compose.yml
|
||||||
|
|
||||||
|
# Порт сервера
|
||||||
|
PORT=3336
|
||||||
|
|
||||||
|
# Режим работы
|
||||||
|
NODE_ENV=production
|
||||||
|
|
||||||
|
# База данных (путь относительно корня проекта)
|
||||||
|
# DB_PATH=./data/poker.db
|
||||||
|
|
||||||
|
# Настройки JWT (опционально, генерируются автоматически)
|
||||||
|
# JWT_SECRET=your-secret-key-here
|
||||||
|
# JWT_EXPIRES_IN=7d
|
||||||
|
|
@ -1 +1,56 @@
|
||||||
node_modules/*
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# Database
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
data/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*.swn
|
||||||
|
*~
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Docker volumes
|
||||||
|
data/
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
.tmp/
|
||||||
|
temp/
|
||||||
|
tmp/
|
||||||
|
|
||||||
|
# Build files
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,411 @@
|
||||||
|
# Управление пользователями (Админ-панель)
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
Добавлена полноценная система управления пользователями в админ-панели с возможностью просмотра статистики, фильтрации, редактирования, блокировки и удаления пользователей.
|
||||||
|
|
||||||
|
## Новые возможности
|
||||||
|
|
||||||
|
### 📊 Статистика пользователей
|
||||||
|
- **Всего пользователей** — общее количество зарегистрированных
|
||||||
|
- **Активных (сегодня)** — пользователи с входом сегодня
|
||||||
|
- **Заблокировано** — количество заблокированных аккаунтов
|
||||||
|
- **Админов** — количество администраторов
|
||||||
|
|
||||||
|
### 🔍 Фильтры
|
||||||
|
- **По роли**: Все / Админы / Пользователи
|
||||||
|
- **По статусу**: Все / Активные / Заблокированные
|
||||||
|
- **Поиск по логину**: Динамический поиск
|
||||||
|
|
||||||
|
### 📋 Таблица пользователей
|
||||||
|
|
||||||
|
Отображаемые данные:
|
||||||
|
- **Логин** — имя пользователя (с пометкой "Вы" для текущего)
|
||||||
|
- **Роль** — 👑 Админ или 👤 Пользователь
|
||||||
|
- **IP адрес** — последний известный IP
|
||||||
|
- **Последний вход** — относительное время ("2 ч назад", "Вчера")
|
||||||
|
- **Статус** — ✅ Активен или 🚫 Заблокирован
|
||||||
|
- **Действия** — кнопки управления
|
||||||
|
|
||||||
|
### ⚙️ Действия с пользователями
|
||||||
|
|
||||||
|
1. **✏️ Редактировать**
|
||||||
|
- Изменить роль (Админ/Пользователь)
|
||||||
|
- Изменить статус (Активен/Заблокирован)
|
||||||
|
- Сменить пароль (опционально)
|
||||||
|
|
||||||
|
2. **🚫/✅ Заблокировать/Разблокировать**
|
||||||
|
- Быстрая блокировка/разблокировка
|
||||||
|
- Заблокированные пользователи не могут войти
|
||||||
|
|
||||||
|
3. **🗑️ Удалить**
|
||||||
|
- Полное удаление пользователя
|
||||||
|
- Требует двойного подтверждения
|
||||||
|
- Нельзя удалить себя
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### Открыть вкладку "Пользователи"
|
||||||
|
|
||||||
|
1. Войдите как администратор (`admin` / `admin`)
|
||||||
|
2. Откройте **Админ-панель** (меню → ⚙️ Админ)
|
||||||
|
3. Выберите вкладку **👥 Пользователи**
|
||||||
|
|
||||||
|
### Просмотр и фильтрация
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ [Все роли ▼] [Все статусы ▼] [🔍 Поиск...] │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- Выберите фильтры в выпадающих списках
|
||||||
|
- Введите текст в поле поиска для фильтрации по логину
|
||||||
|
- Результаты обновляются автоматически
|
||||||
|
|
||||||
|
### Редактирование пользователя
|
||||||
|
|
||||||
|
1. Нажмите **✏️** напротив пользователя
|
||||||
|
2. Откроется модальное окно:
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ ✏️ Редактирование пользователя │
|
||||||
|
├─────────────────────────────────┤
|
||||||
|
│ Логин: [username] (readonly) │
|
||||||
|
│ Роль: [Пользователь ▼] │
|
||||||
|
│ Статус: [Активен ▼] │
|
||||||
|
│ Новый пароль: [________] │
|
||||||
|
│ │
|
||||||
|
│ [💾 Сохранить] [❌ Отмена] │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
3. Внесите изменения
|
||||||
|
4. Нажмите **💾 Сохранить**
|
||||||
|
|
||||||
|
### Блокировка пользователя
|
||||||
|
|
||||||
|
**Быстрая блокировка:**
|
||||||
|
- Нажмите **🚫** напротив пользователя
|
||||||
|
- Подтвердите действие
|
||||||
|
- Пользователь заблокирован
|
||||||
|
|
||||||
|
**Эффект:**
|
||||||
|
- Пользователь не сможет войти в систему
|
||||||
|
- При попытке входа: "Ваш аккаунт заблокирован"
|
||||||
|
- Статус в таблице: 🚫 Заблокирован
|
||||||
|
|
||||||
|
**Разблокировка:**
|
||||||
|
- Нажмите **✅** напротив заблокированного пользователя
|
||||||
|
- Подтвердите действие
|
||||||
|
|
||||||
|
### Удаление пользователя
|
||||||
|
|
||||||
|
⚠️ **ВНИМАНИЕ: Действие необратимо!**
|
||||||
|
|
||||||
|
1. Нажмите **🗑️** напротив пользователя
|
||||||
|
2. Подтвердите удаление (1-е окно)
|
||||||
|
3. Подтвердите ещё раз (2-е окно для безопасности)
|
||||||
|
4. Пользователь удалён полностью
|
||||||
|
|
||||||
|
**Что удаляется:**
|
||||||
|
- Запись пользователя из БД
|
||||||
|
- Настройки пользователя
|
||||||
|
- История игр сохраняется для статистики
|
||||||
|
|
||||||
|
**Ограничения:**
|
||||||
|
- Нельзя удалить себя
|
||||||
|
- Нельзя удалить единственного админа (рекомендуется)
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### POST `/api/admin/users`
|
||||||
|
|
||||||
|
Получить список пользователей с фильтрами.
|
||||||
|
|
||||||
|
**Headers:**
|
||||||
|
```
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"role": "admin|user|",
|
||||||
|
"status": "active|banned|",
|
||||||
|
"search": "логин для поиска"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"username": "admin",
|
||||||
|
"role": "admin",
|
||||||
|
"banned": 0,
|
||||||
|
"ipAddress": "127.0.0.1",
|
||||||
|
"lastLogin": "2026-02-01T12:00:00Z",
|
||||||
|
"createdAt": "2026-01-01T00:00:00Z",
|
||||||
|
"totalGames": 42,
|
||||||
|
"totalWins": 18,
|
||||||
|
"totalChipsWon": 15000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stats": {
|
||||||
|
"total": 10,
|
||||||
|
"active": 3,
|
||||||
|
"banned": 1,
|
||||||
|
"admins": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST `/api/admin/user/update`
|
||||||
|
|
||||||
|
Обновить данные пользователя.
|
||||||
|
|
||||||
|
**Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "user123",
|
||||||
|
"role": "admin",
|
||||||
|
"banned": false,
|
||||||
|
"password": "новый_пароль" // опционально
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST `/api/admin/user/ban`
|
||||||
|
|
||||||
|
Заблокировать/разблокировать пользователя.
|
||||||
|
|
||||||
|
**Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "user123",
|
||||||
|
"banned": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST `/api/admin/user/delete`
|
||||||
|
|
||||||
|
Удалить пользователя.
|
||||||
|
|
||||||
|
**Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "user123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Errors:**
|
||||||
|
- `403` — Нельзя удалить себя
|
||||||
|
- `404` — Пользователь не найден
|
||||||
|
- `500` — Ошибка сервера
|
||||||
|
|
||||||
|
## База данных
|
||||||
|
|
||||||
|
### Новые поля в таблице `users`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE users ADD COLUMN banned INTEGER DEFAULT 0;
|
||||||
|
ALTER TABLE users ADD COLUMN ip_address TEXT;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Описание:**
|
||||||
|
- `banned` — флаг блокировки (0 = активен, 1 = заблокирован)
|
||||||
|
- `ip_address` — последний известный IP адрес пользователя
|
||||||
|
|
||||||
|
### Миграция
|
||||||
|
|
||||||
|
Поля добавляются автоматически при запуске сервера, если их нет:
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ Добавлено поле banned в таблицу users
|
||||||
|
✅ Добавлено поле ip_address в таблицу users
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
### Проверки на сервере
|
||||||
|
|
||||||
|
1. **Авторизация**: Только админы имеют доступ к API
|
||||||
|
2. **Валидация**: Проверка наличия обязательных полей
|
||||||
|
3. **Защита от самоблокировки**: Нельзя заблокировать/удалить себя
|
||||||
|
4. **Проверка при входе**: Блокированные пользователи не могут войти
|
||||||
|
|
||||||
|
### Логирование
|
||||||
|
|
||||||
|
Все административные действия логируются:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
database.logAction(adminId, adminUsername, 'update_user', {
|
||||||
|
target: 'user123',
|
||||||
|
changes: { role: 'admin', banned: true }
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Типы действий:**
|
||||||
|
- `view_users` — просмотр списка пользователей
|
||||||
|
- `update_user` — изменение данных пользователя
|
||||||
|
- `ban_user` — блокировка пользователя
|
||||||
|
- `unban_user` — разблокировка пользователя
|
||||||
|
- `delete_user` — удаление пользователя
|
||||||
|
|
||||||
|
## Интерфейс
|
||||||
|
|
||||||
|
### Цветовая индикация
|
||||||
|
|
||||||
|
**Роли:**
|
||||||
|
- 👑 **Админ** — фиолетовый градиент
|
||||||
|
- 👤 **Пользователь** — серый фон
|
||||||
|
|
||||||
|
**Статусы:**
|
||||||
|
- ✅ **Активен** — зелёный фон
|
||||||
|
- 🚫 **Заблокирован** — красный фон
|
||||||
|
|
||||||
|
### Адаптивность
|
||||||
|
|
||||||
|
- **Desktop (>1024px)**: Полная таблица
|
||||||
|
- **Tablet (768-1024px)**: Компактные столбцы
|
||||||
|
- **Mobile (<768px)**: Горизонтальная прокрутка таблицы
|
||||||
|
|
||||||
|
### Анимации
|
||||||
|
|
||||||
|
- Плавное появление вкладки (fadeInUp 0.3s)
|
||||||
|
- Hover эффекты на строках таблицы
|
||||||
|
- Увеличение кнопок при наведении (scale 1.1)
|
||||||
|
|
||||||
|
## Примеры использования
|
||||||
|
|
||||||
|
### Создать нового админа
|
||||||
|
|
||||||
|
1. Зарегистрируйте пользователя обычным способом
|
||||||
|
2. Войдите как админ
|
||||||
|
3. Откройте вкладку **👥 Пользователи**
|
||||||
|
4. Найдите нужного пользователя
|
||||||
|
5. Нажмите **✏️ Редактировать**
|
||||||
|
6. Измените роль на **👑 Администратор**
|
||||||
|
7. Сохраните
|
||||||
|
|
||||||
|
### Заблокировать спамера
|
||||||
|
|
||||||
|
1. Откройте вкладку **👥 Пользователи**
|
||||||
|
2. Найдите спамера в списке или через поиск
|
||||||
|
3. Нажмите **🚫 Заблокировать**
|
||||||
|
4. Подтвердите действие
|
||||||
|
5. Пользователь заблокирован
|
||||||
|
|
||||||
|
### Посмотреть активных сегодня
|
||||||
|
|
||||||
|
1. Откройте вкладку **👥 Пользователи**
|
||||||
|
2. В верхней панели статистики смотрите "Активных (сегодня)"
|
||||||
|
3. Или используйте фильтр и отсортируйте по "Последний вход"
|
||||||
|
|
||||||
|
### Сбросить пароль пользователю
|
||||||
|
|
||||||
|
1. Откройте вкладку **👥 Пользователи**
|
||||||
|
2. Найдите пользователя
|
||||||
|
3. Нажмите **✏️ Редактировать**
|
||||||
|
4. Введите новый пароль в поле "Новый пароль"
|
||||||
|
5. Нажмите **💾 Сохранить**
|
||||||
|
6. Сообщите пользователю новый пароль
|
||||||
|
|
||||||
|
## Ограничения и рекомендации
|
||||||
|
|
||||||
|
### Ограничения
|
||||||
|
|
||||||
|
- ❌ Нельзя редактировать/удалять себя
|
||||||
|
- ❌ Нельзя заблокировать себя
|
||||||
|
- ❌ Удаление необратимо (нет корзины)
|
||||||
|
|
||||||
|
### Рекомендации
|
||||||
|
|
||||||
|
- ✅ Всегда имейте минимум 2 админов
|
||||||
|
- ✅ Используйте блокировку вместо удаления (можно отменить)
|
||||||
|
- ✅ Регулярно проверяйте список активных пользователей
|
||||||
|
- ✅ Следите за IP адресами для выявления дубликатов
|
||||||
|
- ✅ Логируйте важные действия через вкладку "📋 Логи"
|
||||||
|
|
||||||
|
## Отладка
|
||||||
|
|
||||||
|
### Проверка прав доступа
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// В консоли браузера
|
||||||
|
console.log(currentUser?.role); // Должно быть "admin"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка токена
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// В консоли браузера
|
||||||
|
console.log(localStorage.getItem('token'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка API
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Получить список пользователей
|
||||||
|
curl -X POST http://localhost:3000/api/admin/users \
|
||||||
|
-H "Authorization: Bearer <token>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"role":"","status":"","search":""}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Логи действий
|
||||||
|
|
||||||
|
Все административные действия записываются в таблицу `action_logs`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT * FROM action_logs
|
||||||
|
WHERE action_type IN ('update_user', 'ban_user', 'delete_user')
|
||||||
|
ORDER BY timestamp DESC
|
||||||
|
LIMIT 20;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обновления
|
||||||
|
|
||||||
|
**Версия 1.0** (2026-02-01)
|
||||||
|
- ✅ Базовое управление пользователями
|
||||||
|
- ✅ Фильтры и поиск
|
||||||
|
- ✅ Редактирование, блокировка, удаление
|
||||||
|
- ✅ Статистика пользователей
|
||||||
|
- ✅ Отслеживание IP адресов
|
||||||
|
- ✅ Проверка блокировки при входе
|
||||||
|
|
||||||
|
**Планируется:**
|
||||||
|
- 📋 Экспорт списка пользователей в CSV
|
||||||
|
- 📊 Расширенная статистика по пользователям
|
||||||
|
- 🔍 Фильтр по дате регистрации
|
||||||
|
- 📧 Отправка уведомлений пользователям
|
||||||
|
- 🔐 Двухфакторная аутентификация для админов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Документация актуальна для версии**: 1.0
|
||||||
|
**Дата**: 2026-02-01
|
||||||
|
**Автор**: GitHub Copilot
|
||||||
|
|
@ -0,0 +1,439 @@
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName poker.yourdomain.com
|
||||||
|
ServerAdmin admin@yourdomain.com
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/poker_error.log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/poker_access.log combined
|
||||||
|
|
||||||
|
# Проксирование на Docker контейнер
|
||||||
|
ProxyPreserveHost On
|
||||||
|
ProxyPass / http://localhost:3336/
|
||||||
|
ProxyPassReverse / http://localhost:3336/
|
||||||
|
|
||||||
|
# WebSocket поддержка
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{HTTP:Upgrade} =websocket [NC]
|
||||||
|
RewriteRule /(.*) ws://localhost:3336/$1 [P,L]
|
||||||
|
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
|
||||||
|
RewriteRule /(.*) http://localhost:3336/$1 [P,L]
|
||||||
|
|
||||||
|
# Заголовки для WebSocket
|
||||||
|
<Location />
|
||||||
|
ProxyPass http://localhost:3336/
|
||||||
|
ProxyPassReverse http://localhost:3336/
|
||||||
|
ProxyPreserveHost On
|
||||||
|
|
||||||
|
# WebSocket headers
|
||||||
|
RequestHeader set X-Forwarded-Proto "http"
|
||||||
|
RequestHeader set X-Forwarded-Port "80"
|
||||||
|
</Location>
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
# SSL версия (если используете HTTPS)
|
||||||
|
<IfModule mod_ssl.c>
|
||||||
|
<VirtualHost *:443>
|
||||||
|
ServerName poker.yourdomain.com
|
||||||
|
ServerAdmin admin@yourdomain.com
|
||||||
|
|
||||||
|
# SSL сертификаты
|
||||||
|
SSLEngine on
|
||||||
|
SSLCertificateFile /etc/ssl/certs/poker.crt
|
||||||
|
SSLCertificateKeyFile /etc/ssl/private/poker.key
|
||||||
|
# SSLCertificateChainFile /etc/ssl/certs/chain.pem
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/poker_ssl_error.log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/poker_ssl_access.log combined
|
||||||
|
|
||||||
|
# Проксирование на Docker контейнер
|
||||||
|
ProxyPreserveHost On
|
||||||
|
ProxyPass / http://localhost:3336/
|
||||||
|
ProxyPassReverse / http://localhost:3336/
|
||||||
|
|
||||||
|
# WebSocket поддержка
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{HTTP:Upgrade} =websocket [NC]
|
||||||
|
RewriteRule /(.*) ws://localhost:3336/$1 [P,L]
|
||||||
|
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
|
||||||
|
RewriteRule /(.*) http://localhost:3336/$1 [P,L]
|
||||||
|
|
||||||
|
# Заголовки для WebSocket
|
||||||
|
<Location />
|
||||||
|
ProxyPass http://localhost:3336/
|
||||||
|
ProxyPassReverse http://localhost:3336/
|
||||||
|
ProxyPreserveHost On
|
||||||
|
|
||||||
|
# WebSocket headers
|
||||||
|
RequestHeader set X-Forwarded-Proto "https"
|
||||||
|
RequestHeader set X-Forwarded-Port "443"
|
||||||
|
</Location>
|
||||||
|
|
||||||
|
# Безопасность
|
||||||
|
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||||
|
Header always set X-Frame-Options "SAMEORIGIN"
|
||||||
|
Header always set X-Content-Type-Options "nosniff"
|
||||||
|
</VirtualHost>
|
||||||
|
</IfModule>
|
||||||
|
# 🃏 Texas Hold'em Poker - Развертывание в Docker
|
||||||
|
|
||||||
|
## 📋 Содержание
|
||||||
|
|
||||||
|
- [Быстрый старт](#быстрый-старт)
|
||||||
|
- [Развертывание с Docker](#развертывание-с-docker)
|
||||||
|
- [Настройка Apache](#настройка-apache)
|
||||||
|
- [Переменные окружения](#переменные-окружения)
|
||||||
|
- [Backup и восстановление](#backup-и-восстановление)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
### Запуск через Docker Compose (рекомендуется)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Клонируйте репозиторий
|
||||||
|
git clone <repository-url>
|
||||||
|
cd onlinepocker
|
||||||
|
|
||||||
|
# Запустите контейнер
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Проверьте статус
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# Просмотр логов
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Приложение будет доступно по адресу: `http://localhost:3336`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐳 Развертывание с Docker
|
||||||
|
|
||||||
|
### Сборка Docker образа вручную
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Сборка образа
|
||||||
|
docker build -t texas-holdem-poker:latest .
|
||||||
|
|
||||||
|
# Запуск контейнера
|
||||||
|
docker run -d \
|
||||||
|
--name poker-app \
|
||||||
|
-p 3336:3336 \
|
||||||
|
-v $(pwd)/data:/app/data \
|
||||||
|
-e NODE_ENV=production \
|
||||||
|
-e PORT=3336 \
|
||||||
|
texas-holdem-poker:latest
|
||||||
|
|
||||||
|
# Проверка работы
|
||||||
|
docker ps
|
||||||
|
docker logs poker-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Управление контейнером
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Остановка
|
||||||
|
docker stop poker-app
|
||||||
|
|
||||||
|
# Запуск
|
||||||
|
docker start poker-app
|
||||||
|
|
||||||
|
# Перезапуск
|
||||||
|
docker restart poker-app
|
||||||
|
|
||||||
|
# Удаление
|
||||||
|
docker stop poker-app
|
||||||
|
docker rm poker-app
|
||||||
|
|
||||||
|
# Просмотр логов
|
||||||
|
docker logs -f poker-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запуск в фоне
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Остановка
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Перезапуск
|
||||||
|
docker-compose restart
|
||||||
|
|
||||||
|
# Пересборка образа
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Пересборка и запуск
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Просмотр логов
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Остановка с удалением volumes
|
||||||
|
docker-compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Настройка Apache
|
||||||
|
|
||||||
|
### Включение необходимых модулей Apache
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Включаем модули для проксирования и WebSocket
|
||||||
|
sudo a2enmod proxy
|
||||||
|
sudo a2enmod proxy_http
|
||||||
|
sudo a2enmod proxy_wstunnel
|
||||||
|
sudo a2enmod rewrite
|
||||||
|
sudo a2enmod headers
|
||||||
|
sudo a2enmod ssl # Если используете HTTPS
|
||||||
|
|
||||||
|
# Перезапуск Apache
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Копирование конфигурации
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Копируем конфиг в Apache
|
||||||
|
sudo cp apache-config.conf /etc/apache2/sites-available/poker.conf
|
||||||
|
|
||||||
|
# Редактируем домен (замените poker.yourdomain.com на ваш домен)
|
||||||
|
sudo nano /etc/apache2/sites-available/poker.conf
|
||||||
|
|
||||||
|
# Включаем сайт
|
||||||
|
sudo a2ensite poker.conf
|
||||||
|
|
||||||
|
# Проверяем конфигурацию
|
||||||
|
sudo apache2ctl configtest
|
||||||
|
|
||||||
|
# Перезагружаем Apache
|
||||||
|
sudo systemctl reload apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка статуса Apache
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверка статуса
|
||||||
|
sudo systemctl status apache2
|
||||||
|
|
||||||
|
# Просмотр логов ошибок
|
||||||
|
sudo tail -f /var/log/apache2/poker_error.log
|
||||||
|
|
||||||
|
# Просмотр логов доступа
|
||||||
|
sudo tail -f /var/log/apache2/poker_access.log
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Переменные окружения
|
||||||
|
|
||||||
|
### Основные переменные
|
||||||
|
|
||||||
|
| Переменная | Значение по умолчанию | Описание |
|
||||||
|
|------------|----------------------|----------|
|
||||||
|
| `PORT` | 3336 | Порт для запуска сервера |
|
||||||
|
| `NODE_ENV` | production | Режим работы (production/development) |
|
||||||
|
|
||||||
|
### Настройка в docker-compose.yml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3336
|
||||||
|
```
|
||||||
|
|
||||||
|
### Настройка в Dockerfile
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=3336
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Структура проекта после развертывания
|
||||||
|
|
||||||
|
```
|
||||||
|
onlinepocker/
|
||||||
|
├── data/ # База данных (создается автоматически)
|
||||||
|
│ └── poker.db
|
||||||
|
├── logs/ # Логи приложения (опционально)
|
||||||
|
├── public/ # Статические файлы
|
||||||
|
├── node_modules/ # Зависимости
|
||||||
|
├── server.js # Основной сервер
|
||||||
|
├── database.js # Работа с БД
|
||||||
|
├── package.json
|
||||||
|
├── Dockerfile # Docker образ
|
||||||
|
├── docker-compose.yml # Docker Compose конфигурация
|
||||||
|
├── .dockerignore # Исключения для Docker
|
||||||
|
└── apache-config.conf # Конфигурация Apache
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💾 Backup и восстановление
|
||||||
|
|
||||||
|
### Создание резервной копии базы данных
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Остановка контейнера
|
||||||
|
docker-compose stop
|
||||||
|
|
||||||
|
# Копирование БД
|
||||||
|
cp data/poker.db data/poker.db.backup.$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
# Запуск контейнера
|
||||||
|
docker-compose start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Восстановление из резервной копии
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Остановка контейнера
|
||||||
|
docker-compose stop
|
||||||
|
|
||||||
|
# Восстановление БД
|
||||||
|
cp data/poker.db.backup.20260201_120000 data/poker.db
|
||||||
|
|
||||||
|
# Запуск контейнера
|
||||||
|
docker-compose start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Автоматический backup (cron)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Создаем скрипт backup
|
||||||
|
nano /opt/poker-backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
BACKUP_DIR="/opt/poker-backups"
|
||||||
|
DATE=$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
# Создаем директорию для backup
|
||||||
|
mkdir -p $BACKUP_DIR
|
||||||
|
|
||||||
|
# Копируем БД
|
||||||
|
docker cp texas-holdem-poker:/app/data/poker.db $BACKUP_DIR/poker.db.$DATE
|
||||||
|
|
||||||
|
# Удаляем старые backup (старше 30 дней)
|
||||||
|
find $BACKUP_DIR -name "poker.db.*" -mtime +30 -delete
|
||||||
|
|
||||||
|
echo "Backup создан: poker.db.$DATE"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Делаем скрипт исполняемым
|
||||||
|
chmod +x /opt/poker-backup.sh
|
||||||
|
|
||||||
|
# Добавляем в cron (каждый день в 3:00)
|
||||||
|
crontab -e
|
||||||
|
# Добавьте строку:
|
||||||
|
0 3 * * * /opt/poker-backup.sh >> /var/log/poker-backup.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
|
### Проблемы с портами
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверка, не занят ли порт 3336
|
||||||
|
sudo netstat -tlnp | grep 3336
|
||||||
|
|
||||||
|
# Если порт занят, найти процесс
|
||||||
|
sudo lsof -i :3336
|
||||||
|
|
||||||
|
# Убить процесс
|
||||||
|
sudo kill -9 <PID>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проблемы с WebSocket
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверка модулей Apache
|
||||||
|
apache2ctl -M | grep proxy
|
||||||
|
|
||||||
|
# Должны быть включены:
|
||||||
|
# - proxy_module
|
||||||
|
# - proxy_http_module
|
||||||
|
# - proxy_wstunnel_module
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проблемы с правами доступа
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Установка правильных прав на директорию data
|
||||||
|
sudo chown -R www-data:www-data data/
|
||||||
|
sudo chmod 755 data/
|
||||||
|
|
||||||
|
# Для Docker
|
||||||
|
sudo chown -R 1000:1000 data/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Просмотр логов контейнера
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Все логи
|
||||||
|
docker logs texas-holdem-poker
|
||||||
|
|
||||||
|
# Последние 100 строк
|
||||||
|
docker logs --tail 100 texas-holdem-poker
|
||||||
|
|
||||||
|
# В реальном времени
|
||||||
|
docker logs -f texas-holdem-poker
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Безопасность
|
||||||
|
|
||||||
|
### Рекомендации
|
||||||
|
|
||||||
|
1. **Используйте HTTPS** - настройте SSL сертификаты (Let's Encrypt)
|
||||||
|
2. **Firewall** - ограничьте доступ к порту 3336 только с localhost
|
||||||
|
3. **Регулярные обновления** - обновляйте зависимости и Docker образы
|
||||||
|
4. **Backup** - настройте автоматическое резервное копирование
|
||||||
|
5. **Мониторинг** - отслеживайте логи и производительность
|
||||||
|
|
||||||
|
### Настройка Let's Encrypt (опционально)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Установка certbot
|
||||||
|
sudo apt-get install certbot python3-certbot-apache
|
||||||
|
|
||||||
|
# Получение сертификата
|
||||||
|
sudo certbot --apache -d poker.yourdomain.com
|
||||||
|
|
||||||
|
# Автоматическое обновление
|
||||||
|
sudo certbot renew --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Поддержка
|
||||||
|
|
||||||
|
Если возникли проблемы:
|
||||||
|
|
||||||
|
1. Проверьте логи: `docker-compose logs -f`
|
||||||
|
2. Проверьте статус: `docker-compose ps`
|
||||||
|
3. Проверьте Apache: `sudo systemctl status apache2`
|
||||||
|
4. Проверьте логи Apache: `sudo tail -f /var/log/apache2/poker_error.log`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Лицензия
|
||||||
|
|
||||||
|
Этот проект предназначен для личного использования.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Версия:** 1.0.0
|
||||||
|
**Дата обновления:** 2026-02-01
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
# 🐳 Docker Quick Start
|
||||||
|
|
||||||
|
## Быстрый запуск
|
||||||
|
|
||||||
|
### Windows (PowerShell)
|
||||||
|
```powershell
|
||||||
|
.\deploy.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux/Mac (Bash)
|
||||||
|
```bash
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ручной запуск
|
||||||
|
```bash
|
||||||
|
# Сборка и запуск
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Просмотр логов
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Остановка
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Доступ к приложению
|
||||||
|
|
||||||
|
После запуска приложение будет доступно по адресу:
|
||||||
|
- **http://localhost:3336**
|
||||||
|
|
||||||
|
## Полезные команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Просмотр статуса
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# Просмотр логов
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Перезапуск
|
||||||
|
docker-compose restart
|
||||||
|
|
||||||
|
# Остановка
|
||||||
|
docker-compose stop
|
||||||
|
|
||||||
|
# Запуск после остановки
|
||||||
|
docker-compose start
|
||||||
|
|
||||||
|
# Полная остановка с удалением контейнеров
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Пересборка образа
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## NPM скрипты
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Сборка Docker образа
|
||||||
|
npm run docker:build
|
||||||
|
|
||||||
|
# Запуск контейнера
|
||||||
|
npm run docker:up
|
||||||
|
|
||||||
|
# Остановка контейнера
|
||||||
|
npm run docker:down
|
||||||
|
|
||||||
|
# Просмотр логов
|
||||||
|
npm run docker:logs
|
||||||
|
|
||||||
|
# Перезапуск
|
||||||
|
npm run docker:restart
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура данных
|
||||||
|
|
||||||
|
```
|
||||||
|
data/ # База данных SQLite (автоматически создается)
|
||||||
|
logs/ # Логи приложения (опционально)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройка Apache
|
||||||
|
|
||||||
|
Подробная инструкция по настройке Apache для проксирования находится в файле `DEPLOYMENT.md`.
|
||||||
|
|
||||||
|
Основные шаги:
|
||||||
|
1. Включить модули Apache: `proxy`, `proxy_http`, `proxy_wstunnel`, `rewrite`
|
||||||
|
2. Скопировать `apache-config.conf` в `/etc/apache2/sites-available/`
|
||||||
|
3. Включить сайт: `sudo a2ensite poker.conf`
|
||||||
|
4. Перезагрузить Apache: `sudo systemctl reload apache2`
|
||||||
|
|
||||||
|
## Порты
|
||||||
|
|
||||||
|
- **Внутренний порт контейнера**: 3336
|
||||||
|
- **Внешний порт**: 3336
|
||||||
|
- **Apache проксирует** запросы на localhost:3336
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Порт занят
|
||||||
|
```bash
|
||||||
|
# Проверка занятости порта
|
||||||
|
netstat -ano | findstr :3336 # Windows
|
||||||
|
lsof -i :3336 # Linux/Mac
|
||||||
|
|
||||||
|
# Остановка существующего контейнера
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проблемы с правами доступа (Linux)
|
||||||
|
```bash
|
||||||
|
sudo chown -R $USER:$USER data/
|
||||||
|
sudo chmod 755 data/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Просмотр детальных логов
|
||||||
|
```bash
|
||||||
|
docker-compose logs --tail=100 poker-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Документация
|
||||||
|
|
||||||
|
- **Полная инструкция по развертыванию**: `DEPLOYMENT.md`
|
||||||
|
- **Настройка системных промптов ботов**: `BOT_PERSONALITIES_CONFIG.md`
|
||||||
|
- **Управление пользователями**: `ADMIN_USER_MANAGEMENT.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Версия**: 1.0.0
|
||||||
|
**Порт**: 3336
|
||||||
|
**Docker**: ✅ Готово к использованию
|
||||||
|
|
@ -0,0 +1,281 @@
|
||||||
|
# 🐳 Подготовка к Docker развертыванию - Сводка изменений
|
||||||
|
|
||||||
|
## ✅ Созданные файлы
|
||||||
|
|
||||||
|
### Docker конфигурация
|
||||||
|
|
||||||
|
1. **`Dockerfile`**
|
||||||
|
- Основан на Node.js 18 Alpine
|
||||||
|
- Порт: 3336
|
||||||
|
- Автоматическая установка зависимостей
|
||||||
|
- Production режим
|
||||||
|
|
||||||
|
2. **`docker-compose.yml`**
|
||||||
|
- Сервис: poker-app
|
||||||
|
- Порт маппинг: 3336:3336
|
||||||
|
- Volumes для данных и логов
|
||||||
|
- Автоматический рестарт
|
||||||
|
|
||||||
|
3. **`.dockerignore`**
|
||||||
|
- Исключение node_modules
|
||||||
|
- Исключение баз данных
|
||||||
|
- Исключение логов и временных файлов
|
||||||
|
|
||||||
|
### Apache конфигурация
|
||||||
|
|
||||||
|
4. **`apache-config.conf`**
|
||||||
|
- HTTP (порт 80) → localhost:3336
|
||||||
|
- HTTPS (порт 443) → localhost:3336
|
||||||
|
- WebSocket поддержка (proxy_wstunnel)
|
||||||
|
- Заголовки безопасности
|
||||||
|
- Готовые настройки SSL
|
||||||
|
|
||||||
|
### Скрипты развертывания
|
||||||
|
|
||||||
|
5. **`deploy.sh`** (Linux/Mac)
|
||||||
|
- Проверка Docker и Docker Compose
|
||||||
|
- Создание директорий
|
||||||
|
- Автоматическая сборка и запуск
|
||||||
|
- Проверка статуса
|
||||||
|
|
||||||
|
6. **`deploy.ps1`** (Windows PowerShell)
|
||||||
|
- Аналог deploy.sh для Windows
|
||||||
|
- Цветной вывод
|
||||||
|
- Проверка всех зависимостей
|
||||||
|
|
||||||
|
### Документация
|
||||||
|
|
||||||
|
7. **`DEPLOYMENT.md`**
|
||||||
|
- Полное руководство по развертыванию
|
||||||
|
- Инструкции для Docker
|
||||||
|
- Настройка Apache
|
||||||
|
- Backup и восстановление
|
||||||
|
- Troubleshooting
|
||||||
|
- Безопасность
|
||||||
|
|
||||||
|
8. **`DOCKER_README.md`**
|
||||||
|
- Краткая инструкция по Docker
|
||||||
|
- Основные команды
|
||||||
|
- NPM скрипты
|
||||||
|
- Quick start
|
||||||
|
|
||||||
|
9. **`README.md`**
|
||||||
|
- Обновленный главный README
|
||||||
|
- Информация о Docker
|
||||||
|
- Полный список функций
|
||||||
|
- Структура проекта
|
||||||
|
- Troubleshooting
|
||||||
|
|
||||||
|
### Дополнительные файлы
|
||||||
|
|
||||||
|
10. **`.env.example`**
|
||||||
|
- Пример переменных окружения
|
||||||
|
- PORT=3336
|
||||||
|
- NODE_ENV=production
|
||||||
|
|
||||||
|
11. **`.gitignore`**
|
||||||
|
- Обновлен для Docker
|
||||||
|
- Исключение data/ и logs/
|
||||||
|
- Исключение .env файлов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Измененные файлы
|
||||||
|
|
||||||
|
### 1. `package.json`
|
||||||
|
**Добавлены npm скрипты:**
|
||||||
|
```json
|
||||||
|
"docker:build": "docker-compose build",
|
||||||
|
"docker:up": "docker-compose up -d",
|
||||||
|
"docker:down": "docker-compose down",
|
||||||
|
"docker:logs": "docker-compose logs -f",
|
||||||
|
"docker:restart": "docker-compose restart"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `public/main.js`
|
||||||
|
**Исправлена работа с токенами:**
|
||||||
|
- Изменено `localStorage.getItem('token')` на `localStorage.getItem('authToken')`
|
||||||
|
- Синхронизировано с auth.js
|
||||||
|
- Исправлены функции: loadUsers(), saveUserEdit(), toggleBanUser(), deleteUser()
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Структура Docker развертывания
|
||||||
|
|
||||||
|
```
|
||||||
|
Приложение (порт 3336)
|
||||||
|
↓
|
||||||
|
Docker контейнер (texas-holdem-poker)
|
||||||
|
↓
|
||||||
|
Docker Compose (управление)
|
||||||
|
↓
|
||||||
|
Apache (reverse proxy)
|
||||||
|
↓
|
||||||
|
Интернет (порт 80/443)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Способы запуска
|
||||||
|
|
||||||
|
### 1. Автоматический (рекомендуется)
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
```powershell
|
||||||
|
.\deploy.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
```bash
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Docker Compose
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. NPM скрипты
|
||||||
|
```bash
|
||||||
|
npm run docker:build
|
||||||
|
npm run docker:up
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Docker вручную
|
||||||
|
```bash
|
||||||
|
docker build -t texas-holdem-poker .
|
||||||
|
docker run -d -p 3336:3336 --name poker-app texas-holdem-poker
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Порты
|
||||||
|
|
||||||
|
- **3336** - Основной порт приложения (Docker)
|
||||||
|
- **80** - HTTP через Apache (опционально)
|
||||||
|
- **443** - HTTPS через Apache (опционально)
|
||||||
|
|
||||||
|
**Важно:** Порт изменен с 3000 на 3336 согласно требованию
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Volumes
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data # База данных SQLite
|
||||||
|
- ./logs:/app/logs # Логи приложения
|
||||||
|
```
|
||||||
|
|
||||||
|
Данные сохраняются на хосте, даже при удалении контейнера.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Переменные окружения
|
||||||
|
|
||||||
|
В `docker-compose.yml`:
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3336
|
||||||
|
```
|
||||||
|
|
||||||
|
В `Dockerfile`:
|
||||||
|
```dockerfile
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=3336
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Документация
|
||||||
|
|
||||||
|
| Файл | Описание |
|
||||||
|
|------|----------|
|
||||||
|
| `README.md` | Главная документация проекта |
|
||||||
|
| `DEPLOYMENT.md` | Полное руководство по развертыванию |
|
||||||
|
| `DOCKER_README.md` | Краткая инструкция Docker |
|
||||||
|
| `BOT_PERSONALITIES_CONFIG.md` | Настройка ботов |
|
||||||
|
| `ADMIN_USER_MANAGEMENT.md` | Управление пользователями |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Контрольный список готовности
|
||||||
|
|
||||||
|
- [x] Dockerfile создан
|
||||||
|
- [x] docker-compose.yml настроен
|
||||||
|
- [x] .dockerignore настроен
|
||||||
|
- [x] Apache конфигурация готова
|
||||||
|
- [x] Скрипты развертывания (Linux & Windows)
|
||||||
|
- [x] Документация обновлена
|
||||||
|
- [x] npm скрипты добавлены
|
||||||
|
- [x] .gitignore обновлен
|
||||||
|
- [x] Порт изменен на 3336
|
||||||
|
- [x] Переменные окружения настроены
|
||||||
|
- [x] Volumes для данных настроены
|
||||||
|
- [x] WebSocket поддержка в Apache
|
||||||
|
- [x] SSL конфигурация готова
|
||||||
|
- [x] Исправлена работа с токенами
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Следующие шаги
|
||||||
|
|
||||||
|
1. **Протестировать локально:**
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Открыть в браузере:**
|
||||||
|
```
|
||||||
|
http://localhost:3336
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Проверить логи:**
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Настроить Apache** (если нужно):
|
||||||
|
```bash
|
||||||
|
sudo cp apache-config.conf /etc/apache2/sites-available/poker.conf
|
||||||
|
sudo a2ensite poker.conf
|
||||||
|
sudo systemctl reload apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Создать администратора:**
|
||||||
|
- Зарегистрироваться через интерфейс
|
||||||
|
- Выполнить SQL: `UPDATE users SET role = 'admin' WHERE username = 'ваш_логин';`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Команды для проверки
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверка статуса
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# Просмотр логов
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Проверка порта
|
||||||
|
netstat -ano | findstr :3336 # Windows
|
||||||
|
lsof -i :3336 # Linux/Mac
|
||||||
|
|
||||||
|
# Проверка Apache модулей
|
||||||
|
apache2ctl -M | grep proxy
|
||||||
|
|
||||||
|
# Тест подключения
|
||||||
|
curl http://localhost:3336
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 Готово!
|
||||||
|
|
||||||
|
Репозиторий полностью подготовлен к развертыванию в Docker на порту **3336** с поддержкой Apache reverse proxy.
|
||||||
|
|
||||||
|
**Дата:** 2026-02-01
|
||||||
|
**Версия:** 1.0.0
|
||||||
|
**Статус:** ✅ Production Ready
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Используем официальный образ Node.js
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
# Устанавливаем рабочую директорию
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем package.json и package-lock.json
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Устанавливаем зависимости
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
# Копируем все файлы приложения
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Создаем директорию для базы данных
|
||||||
|
RUN mkdir -p /app/data
|
||||||
|
|
||||||
|
# Открываем порт 3336
|
||||||
|
EXPOSE 3336
|
||||||
|
|
||||||
|
# Устанавливаем переменные окружения
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=3336
|
||||||
|
|
||||||
|
# Запускаем приложение
|
||||||
|
CMD ["node", "server.js"]
|
||||||
342
README.md
342
README.md
|
|
@ -1,2 +1,342 @@
|
||||||
# poker
|
# 🃏 Texas Hold'em No-Limit Poker
|
||||||
|
|
||||||
|
Полнофункциональная онлайн-игра в покер с поддержкой мультиплеера, ИИ-ботами и интеграцией LLM для реалистичного общения.
|
||||||
|
|
||||||
|
## ✨ Особенности
|
||||||
|
|
||||||
|
- 🎮 **Мультиплеер**: 2-6 игроков в режиме реального времени
|
||||||
|
- 🤖 **ИИ-боты**: 8 уникальных личностей с разными стилями игры
|
||||||
|
- 💬 **LLM-чат**: Реалистичное общение ботов через Ollama/LM Studio/OpenAI
|
||||||
|
- 👤 **Система авторизации**: Регистрация, вход, гостевой режим
|
||||||
|
- 👑 **Админ-панель**: Управление ботами, пользователями и настройками
|
||||||
|
- 🎨 **Современный UI**: Glassmorphism дизайн с анимациями
|
||||||
|
- 🐳 **Docker**: Готов к развертыванию в один клик
|
||||||
|
|
||||||
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
### Вариант 1: Docker (Рекомендуется)
|
||||||
|
|
||||||
|
**Windows (PowerShell):**
|
||||||
|
```powershell
|
||||||
|
.\deploy.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
```bash
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Или вручную:**
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Приложение будет доступно: **http://localhost:3336**
|
||||||
|
|
||||||
|
### Вариант 2: Локальный запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Установка зависимостей
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Запуск сервера
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Приложение будет доступно: **http://localhost:3000**
|
||||||
|
|
||||||
|
## 📋 Требования
|
||||||
|
|
||||||
|
### Docker (рекомендуется)
|
||||||
|
- Docker 20.10+
|
||||||
|
- Docker Compose 1.29+
|
||||||
|
|
||||||
|
### Локальный запуск
|
||||||
|
- Node.js 16+
|
||||||
|
- npm 8+
|
||||||
|
|
||||||
|
## 🎮 Игровые режимы
|
||||||
|
|
||||||
|
### 🎯 Одиночная игра
|
||||||
|
- Игра против 1-5 ИИ-ботов
|
||||||
|
- 3 уровня сложности ИИ
|
||||||
|
- 8 уникальных личностей ботов
|
||||||
|
- Настраиваемые системные промпты
|
||||||
|
|
||||||
|
### 🌐 Мультиплеер
|
||||||
|
- Создание приватных комнат
|
||||||
|
- Поделиться ссылкой для присоединения
|
||||||
|
- Чат в реальном времени
|
||||||
|
- До 6 игроков одновременно
|
||||||
|
|
||||||
|
### 🤖 ИИ-боты с личностями
|
||||||
|
|
||||||
|
1. **Виктор "Акула"** - Агрессивный профессионал
|
||||||
|
2. **Анна "Блефер"** - Мастер обмана
|
||||||
|
3. **Сергей "Математик"** - Аналитик-стратег
|
||||||
|
4. **Мария "Рок"** - Консервативный игрок
|
||||||
|
5. **Дмитрий "Маньяк"** - Непредсказуемый агрессор
|
||||||
|
6. **Елена "Читательница"** - Психолог за столом
|
||||||
|
7. **Игорь "Новичок"** - Неопытный оптимист
|
||||||
|
8. **Ольга "Тильт"** - Эмоциональный игрок
|
||||||
|
|
||||||
|
## 👑 Админ-панель
|
||||||
|
|
||||||
|
### Функции администратора:
|
||||||
|
|
||||||
|
- **⚙️ Настройки LLM**: Подключение Ollama/LM Studio/OpenAI
|
||||||
|
- **🤖 Промпты ботов**: Настройка личностей и поведения
|
||||||
|
- **📋 Логи действий**: Мониторинг активности пользователей
|
||||||
|
- **👥 Управление пользователями**:
|
||||||
|
- Просмотр всех пользователей
|
||||||
|
- Статистика (IP, последняя игра)
|
||||||
|
- Изменение ролей (admin/user)
|
||||||
|
- Блокировка пользователей
|
||||||
|
- Удаление аккаунтов
|
||||||
|
|
||||||
|
### Первый администратор
|
||||||
|
|
||||||
|
При первом запуске создайте админа:
|
||||||
|
```sql
|
||||||
|
-- Используйте любой SQLite клиент для изменения БД
|
||||||
|
UPDATE users SET role = 'admin' WHERE username = 'ваш_логин';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐳 Docker развертывание
|
||||||
|
|
||||||
|
### Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
onlinepocker/
|
||||||
|
├── 📄 Dockerfile # Docker образ
|
||||||
|
├── 📄 docker-compose.yml # Docker Compose конфигурация
|
||||||
|
├── 📄 .dockerignore # Исключения для Docker
|
||||||
|
├── 📄 apache-config.conf # Настройка Apache
|
||||||
|
├── 📄 deploy.sh # Скрипт развертывания (Linux/Mac)
|
||||||
|
├── 📄 deploy.ps1 # Скрипт развертывания (Windows)
|
||||||
|
├── 📚 DEPLOYMENT.md # Полная документация
|
||||||
|
└── 📚 DOCKER_README.md # Краткая инструкция Docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запуск
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Остановка
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Просмотр логов
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Перезапуск
|
||||||
|
docker-compose restart
|
||||||
|
|
||||||
|
# Пересборка
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### NPM скрипты
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run docker:build # Сборка образа
|
||||||
|
npm run docker:up # Запуск контейнера
|
||||||
|
npm run docker:down # Остановка контейнера
|
||||||
|
npm run docker:logs # Просмотр логов
|
||||||
|
npm run docker:restart # Перезапуск
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 Настройка Apache
|
||||||
|
|
||||||
|
Для проксирования через Apache (порт 80/443 → 3336):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Включение модулей
|
||||||
|
sudo a2enmod proxy proxy_http proxy_wstunnel rewrite headers ssl
|
||||||
|
|
||||||
|
# Копирование конфигурации
|
||||||
|
sudo cp apache-config.conf /etc/apache2/sites-available/poker.conf
|
||||||
|
|
||||||
|
# Включение сайта
|
||||||
|
sudo a2ensite poker.conf
|
||||||
|
|
||||||
|
# Перезагрузка Apache
|
||||||
|
sudo systemctl reload apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
Подробнее: `DEPLOYMENT.md`
|
||||||
|
|
||||||
|
## 🔧 Конфигурация
|
||||||
|
|
||||||
|
### Переменные окружения
|
||||||
|
|
||||||
|
| Переменная | По умолчанию | Описание |
|
||||||
|
|------------|--------------|----------|
|
||||||
|
| `PORT` | 3336 | Порт сервера |
|
||||||
|
| `NODE_ENV` | production | Режим работы |
|
||||||
|
|
||||||
|
### Настройка LLM (в админ-панели)
|
||||||
|
|
||||||
|
1. Выберите провайдера: Ollama / LM Studio / OpenAI
|
||||||
|
2. Укажите API URL (например, `http://localhost:11434`)
|
||||||
|
3. Выберите модель (например, `llama3.2`)
|
||||||
|
4. Сохраните настройки
|
||||||
|
|
||||||
|
### Настройка промптов ботов
|
||||||
|
|
||||||
|
1. Войдите как администратор
|
||||||
|
2. Откройте "🤖 Промпты ботов"
|
||||||
|
3. Выберите бота
|
||||||
|
4. Отредактируйте системный промпт
|
||||||
|
5. Сохраните изменения
|
||||||
|
|
||||||
|
## 📦 Технологии
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- **Node.js** - Серверная платформа
|
||||||
|
- **Express.js** - REST API
|
||||||
|
- **WebSocket (ws)** - Реальное время
|
||||||
|
- **SQLite (sql.js)** - База данных
|
||||||
|
- **bcryptjs** - Хеширование паролей
|
||||||
|
- **jsonwebtoken** - JWT авторизация
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **Vanilla JavaScript** - Без фреймворков
|
||||||
|
- **CSS3** - Glassmorphism дизайн
|
||||||
|
- **HTML5** - Семантическая разметка
|
||||||
|
|
||||||
|
### DevOps
|
||||||
|
- **Docker** - Контейнеризация
|
||||||
|
- **Docker Compose** - Оркестрация
|
||||||
|
- **Apache** - Reverse proxy
|
||||||
|
|
||||||
|
## 📁 Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
onlinepocker/
|
||||||
|
├── 📂 public/ # Фронтенд
|
||||||
|
│ ├── index.html # Главная страница
|
||||||
|
│ ├── main.js # Основная логика
|
||||||
|
│ ├── game.js # Игровая логика
|
||||||
|
│ ├── ai.js # ИИ и личности ботов
|
||||||
|
│ ├── auth.js # Авторизация
|
||||||
|
│ └── styles.css # Стили
|
||||||
|
├── 📂 data/ # База данных (создается автоматически)
|
||||||
|
│ └── poker.db
|
||||||
|
├── server.js # WebSocket сервер
|
||||||
|
├── database.js # Работа с БД
|
||||||
|
├── package.json # Зависимости
|
||||||
|
├── Dockerfile # Docker образ
|
||||||
|
├── docker-compose.yml # Docker Compose
|
||||||
|
└── 📚 Документация
|
||||||
|
├── DEPLOYMENT.md # Развертывание
|
||||||
|
├── DOCKER_README.md # Docker инструкции
|
||||||
|
├── BOT_PERSONALITIES_CONFIG.md
|
||||||
|
└── ADMIN_USER_MANAGEMENT.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 Безопасность
|
||||||
|
|
||||||
|
- ✅ JWT токены с истечением
|
||||||
|
- ✅ Bcrypt хеширование паролей
|
||||||
|
- ✅ Middleware авторизации
|
||||||
|
- ✅ Проверка прав администратора
|
||||||
|
- ✅ Логирование действий
|
||||||
|
- ✅ Блокировка пользователей
|
||||||
|
- ✅ IP tracking
|
||||||
|
|
||||||
|
## 💾 Backup базы данных
|
||||||
|
|
||||||
|
### Ручной backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker
|
||||||
|
docker cp texas-holdem-poker:/app/data/poker.db ./backup-$(date +%Y%m%d).db
|
||||||
|
|
||||||
|
# Локально
|
||||||
|
cp data/poker.db data/poker.db.backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Автоматический backup (cron)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Каждый день в 3:00
|
||||||
|
0 3 * * * /opt/poker-backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Подробнее: `DEPLOYMENT.md`
|
||||||
|
|
||||||
|
## 📖 Документация
|
||||||
|
|
||||||
|
- **[DEPLOYMENT.md](DEPLOYMENT.md)** - Полное руководство по развертыванию
|
||||||
|
- **[DOCKER_README.md](DOCKER_README.md)** - Быстрый старт с Docker
|
||||||
|
- **[BOT_PERSONALITIES_CONFIG.md](BOT_PERSONALITIES_CONFIG.md)** - Настройка ботов
|
||||||
|
- **[ADMIN_USER_MANAGEMENT.md](ADMIN_USER_MANAGEMENT.md)** - Управление пользователями
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Порт занят
|
||||||
|
```bash
|
||||||
|
# Проверка
|
||||||
|
netstat -ano | findstr :3336 # Windows
|
||||||
|
lsof -i :3336 # Linux/Mac
|
||||||
|
|
||||||
|
# Решение
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проблемы с WebSocket
|
||||||
|
```bash
|
||||||
|
# Проверка модулей Apache
|
||||||
|
apache2ctl -M | grep proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Права доступа (Linux)
|
||||||
|
```bash
|
||||||
|
sudo chown -R $USER:$USER data/
|
||||||
|
sudo chmod 755 data/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📞 Поддержка
|
||||||
|
|
||||||
|
1. Проверьте логи: `docker-compose logs -f`
|
||||||
|
2. Проверьте статус: `docker-compose ps`
|
||||||
|
3. Прочитайте документацию: `DEPLOYMENT.md`
|
||||||
|
|
||||||
|
## 📝 Лицензия
|
||||||
|
|
||||||
|
MIT License - свободно для личного использования
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Краткая инструкция
|
||||||
|
|
||||||
|
### Для локальной разработки
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
# Откройте http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Для production (Docker)
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
# Откройте http://localhost:3336
|
||||||
|
```
|
||||||
|
|
||||||
|
### Создание администратора
|
||||||
|
```bash
|
||||||
|
# 1. Зарегистрируйтесь через интерфейс
|
||||||
|
# 2. Подключитесь к БД и выполните:
|
||||||
|
UPDATE users SET role = 'admin' WHERE username = 'ваш_логин';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Версия:** 1.0.0
|
||||||
|
**Дата:** 2026-02-01
|
||||||
|
**Статус:** ✅ Готов к production
|
||||||
|
|
||||||
|
🃏 **Хорошей игры!**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
# 🧪 Тестирование Docker развертывания
|
||||||
|
|
||||||
|
## Перед запуском
|
||||||
|
|
||||||
|
Убедитесь, что установлены:
|
||||||
|
- Docker Desktop (Windows/Mac) или Docker Engine (Linux)
|
||||||
|
- Docker Compose
|
||||||
|
|
||||||
|
## Быстрый тест
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
```powershell
|
||||||
|
# Проверка версии Docker
|
||||||
|
docker --version
|
||||||
|
|
||||||
|
# Проверка Docker Compose
|
||||||
|
docker-compose --version
|
||||||
|
|
||||||
|
# Запуск приложения
|
||||||
|
.\deploy.ps1
|
||||||
|
|
||||||
|
# Или
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux/Mac
|
||||||
|
```bash
|
||||||
|
# Проверка версии Docker
|
||||||
|
docker --version
|
||||||
|
|
||||||
|
# Проверка Docker Compose
|
||||||
|
docker-compose --version
|
||||||
|
|
||||||
|
# Запуск приложения
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
|
||||||
|
# Или
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка работы
|
||||||
|
|
||||||
|
1. **Открыть в браузере:**
|
||||||
|
```
|
||||||
|
http://localhost:3336
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Проверить логи:**
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Проверить статус:**
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
Должно быть:
|
||||||
|
```
|
||||||
|
NAME STATUS PORTS
|
||||||
|
texas-holdem-poker Up 0.0.0.0:3336->3336/tcp
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Проверить порт:**
|
||||||
|
```bash
|
||||||
|
# Windows
|
||||||
|
netstat -ano | findstr :3336
|
||||||
|
|
||||||
|
# Linux/Mac
|
||||||
|
lsof -i :3336
|
||||||
|
```
|
||||||
|
|
||||||
|
## Тестовые сценарии
|
||||||
|
|
||||||
|
### 1. Регистрация и вход
|
||||||
|
- Откройте http://localhost:3336
|
||||||
|
- Зарегистрируйте нового пользователя
|
||||||
|
- Войдите в систему
|
||||||
|
- Проверьте, что токен сохранился
|
||||||
|
|
||||||
|
### 2. Создание игры
|
||||||
|
- Создайте одиночную игру
|
||||||
|
- Добавьте ботов
|
||||||
|
- Проверьте работу игры
|
||||||
|
|
||||||
|
### 3. Админ панель (если есть доступ)
|
||||||
|
- Войдите как администратор
|
||||||
|
- Откройте админ панель
|
||||||
|
- Проверьте вкладку "Пользователи"
|
||||||
|
- Проверьте логи
|
||||||
|
|
||||||
|
### 4. WebSocket
|
||||||
|
- Создайте мультиплеер комнату
|
||||||
|
- Скопируйте ссылку
|
||||||
|
- Откройте в другой вкладке
|
||||||
|
- Проверьте синхронизацию
|
||||||
|
|
||||||
|
## Остановка и очистка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Остановка
|
||||||
|
docker-compose stop
|
||||||
|
|
||||||
|
# Остановка и удаление контейнеров
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Полная очистка (включая volumes)
|
||||||
|
docker-compose down -v
|
||||||
|
|
||||||
|
# Удаление образов
|
||||||
|
docker rmi texas-holdem-poker
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Порт 3336 занят
|
||||||
|
```bash
|
||||||
|
# Остановите существующие контейнеры
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Или измените порт в docker-compose.yml
|
||||||
|
ports:
|
||||||
|
- "3337:3336" # Используйте другой внешний порт
|
||||||
|
```
|
||||||
|
|
||||||
|
### Контейнер не запускается
|
||||||
|
```bash
|
||||||
|
# Просмотрите логи
|
||||||
|
docker-compose logs
|
||||||
|
|
||||||
|
# Пересоберите образ
|
||||||
|
docker-compose build --no-cache
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### База данных не создается
|
||||||
|
```bash
|
||||||
|
# Проверьте права доступа к директории data
|
||||||
|
ls -la data/
|
||||||
|
|
||||||
|
# Создайте директорию вручную
|
||||||
|
mkdir -p data
|
||||||
|
chmod 755 data
|
||||||
|
```
|
||||||
|
|
||||||
|
### WebSocket не работает
|
||||||
|
- Проверьте, что браузер поддерживает WebSocket
|
||||||
|
- Откройте консоль разработчика (F12)
|
||||||
|
- Проверьте наличие ошибок подключения
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
После успешного запуска вы должны увидеть:
|
||||||
|
|
||||||
|
1. **В консоли:**
|
||||||
|
```
|
||||||
|
✅ Сервер успешно запущен!
|
||||||
|
📍 Сервер доступен по адресу: http://localhost:3336
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **В браузере:**
|
||||||
|
- Страница авторизации загружается
|
||||||
|
- Стили применены корректно
|
||||||
|
- Анимации работают
|
||||||
|
|
||||||
|
3. **В логах Docker:**
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
```
|
||||||
|
🃏 Texas Hold'em Poker Server 🃏
|
||||||
|
Сервер запущен на http://localhost:3336
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Проверка WebSocket:**
|
||||||
|
- Откройте консоль браузера (F12)
|
||||||
|
- Должны быть сообщения о подключении WebSocket
|
||||||
|
- Нет ошибок 404 или 500
|
||||||
|
|
||||||
|
## Следующие шаги
|
||||||
|
|
||||||
|
После успешного тестирования:
|
||||||
|
|
||||||
|
1. **Настройте Apache** (опционально):
|
||||||
|
- Следуйте инструкциям в `DEPLOYMENT.md`
|
||||||
|
- Настройте SSL сертификаты
|
||||||
|
|
||||||
|
2. **Создайте администратора:**
|
||||||
|
```sql
|
||||||
|
UPDATE users SET role = 'admin' WHERE username = 'ваш_логин';
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Настройте LLM** (опционально):
|
||||||
|
- Откройте админ панель
|
||||||
|
- Настройте Ollama/LM Studio/OpenAI
|
||||||
|
|
||||||
|
4. **Настройте backup:**
|
||||||
|
- Следуйте инструкциям в `DEPLOYMENT.md`
|
||||||
|
- Настройте cron задачу
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Статус:** ✅ Готово к тестированию
|
||||||
|
**Порт:** 3336
|
||||||
|
**Дата:** 2026-02-01
|
||||||
152
database.js
152
database.js
|
|
@ -57,6 +57,8 @@ function createTables() {
|
||||||
username TEXT UNIQUE NOT NULL,
|
username TEXT UNIQUE NOT NULL,
|
||||||
password_hash TEXT NOT NULL,
|
password_hash TEXT NOT NULL,
|
||||||
role TEXT DEFAULT 'user',
|
role TEXT DEFAULT 'user',
|
||||||
|
banned INTEGER DEFAULT 0,
|
||||||
|
ip_address TEXT,
|
||||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
last_login TEXT,
|
last_login TEXT,
|
||||||
total_games INTEGER DEFAULT 0,
|
total_games INTEGER DEFAULT 0,
|
||||||
|
|
@ -65,6 +67,24 @@ function createTables() {
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// Миграция: добавляем поля banned и ip_address если их нет
|
||||||
|
try {
|
||||||
|
const columns = db.exec(`PRAGMA table_info(users)`);
|
||||||
|
const columnNames = columns[0]?.values.map(col => col[1]) || [];
|
||||||
|
|
||||||
|
if (!columnNames.includes('banned')) {
|
||||||
|
db.run(`ALTER TABLE users ADD COLUMN banned INTEGER DEFAULT 0`);
|
||||||
|
console.log('✅ Добавлено поле banned в таблицу users');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!columnNames.includes('ip_address')) {
|
||||||
|
db.run(`ALTER TABLE users ADD COLUMN ip_address TEXT`);
|
||||||
|
console.log('✅ Добавлено поле ip_address в таблицу users');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('⚠️ Миграция таблицы users пропущена:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
// Настройки пользователей
|
// Настройки пользователей
|
||||||
db.run(`
|
db.run(`
|
||||||
CREATE TABLE IF NOT EXISTS user_settings (
|
CREATE TABLE IF NOT EXISTS user_settings (
|
||||||
|
|
@ -246,10 +266,10 @@ function registerUser(username, password) {
|
||||||
/**
|
/**
|
||||||
* Авторизация пользователя
|
* Авторизация пользователя
|
||||||
*/
|
*/
|
||||||
function loginUser(username, password) {
|
function loginUser(username, password, ipAddress = null) {
|
||||||
try {
|
try {
|
||||||
const result = db.exec(`
|
const result = db.exec(`
|
||||||
SELECT id, username, password_hash, role
|
SELECT id, username, password_hash, role, banned
|
||||||
FROM users
|
FROM users
|
||||||
WHERE username = ?
|
WHERE username = ?
|
||||||
`, [username]);
|
`, [username]);
|
||||||
|
|
@ -258,20 +278,31 @@ function loginUser(username, password) {
|
||||||
return { success: false, error: 'Неверный логин или пароль' };
|
return { success: false, error: 'Неверный логин или пароль' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const [userId, storedUsername, passwordHash, role] = result[0].values[0];
|
const [userId, storedUsername, passwordHash, role, banned] = result[0].values[0];
|
||||||
|
|
||||||
|
// Проверка на бан
|
||||||
|
if (banned === 1) {
|
||||||
|
return { success: false, error: 'Ваш аккаунт заблокирован' };
|
||||||
|
}
|
||||||
|
|
||||||
if (!bcrypt.compareSync(password, passwordHash)) {
|
if (!bcrypt.compareSync(password, passwordHash)) {
|
||||||
return { success: false, error: 'Неверный логин или пароль' };
|
return { success: false, error: 'Неверный логин или пароль' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем last_login
|
// Обновляем last_login и IP
|
||||||
db.run(`
|
if (ipAddress) {
|
||||||
UPDATE users SET last_login = datetime('now') WHERE id = ?
|
db.run(`
|
||||||
`, [userId]);
|
UPDATE users SET last_login = datetime('now'), ip_address = ? WHERE id = ?
|
||||||
|
`, [ipAddress, userId]);
|
||||||
|
} else {
|
||||||
|
db.run(`
|
||||||
|
UPDATE users SET last_login = datetime('now') WHERE id = ?
|
||||||
|
`, [userId]);
|
||||||
|
}
|
||||||
saveDatabase();
|
saveDatabase();
|
||||||
|
|
||||||
// Логируем
|
// Логируем
|
||||||
logAction(userId, storedUsername, 'login', { username: storedUsername });
|
logAction(userId, storedUsername, 'login', { username: storedUsername, ip: ipAddress });
|
||||||
|
|
||||||
// Генерируем токен
|
// Генерируем токен
|
||||||
const token = generateToken(userId, storedUsername, role);
|
const token = generateToken(userId, storedUsername, role);
|
||||||
|
|
@ -567,6 +598,107 @@ function clearRoomChat(roomId) {
|
||||||
saveDatabase();
|
saveDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ (АДМИН)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить всех пользователей
|
||||||
|
*/
|
||||||
|
function getAllUsers() {
|
||||||
|
const result = db.exec(`
|
||||||
|
SELECT id, username, role, banned, ip_address, last_login, created_at,
|
||||||
|
total_games, total_wins, total_chips_won
|
||||||
|
FROM users
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (result.length === 0) return [];
|
||||||
|
|
||||||
|
return result[0].values.map(row => ({
|
||||||
|
id: row[0],
|
||||||
|
username: row[1],
|
||||||
|
role: row[2],
|
||||||
|
banned: row[3],
|
||||||
|
ipAddress: row[4],
|
||||||
|
lastLogin: row[5],
|
||||||
|
createdAt: row[6],
|
||||||
|
totalGames: row[7],
|
||||||
|
totalWins: row[8],
|
||||||
|
totalChipsWon: row[9]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить пользователя по логину
|
||||||
|
*/
|
||||||
|
function getUserByUsername(username) {
|
||||||
|
const result = db.exec(`
|
||||||
|
SELECT id, username, role, banned, ip_address, last_login, created_at
|
||||||
|
FROM users
|
||||||
|
WHERE username = ?
|
||||||
|
`, [username]);
|
||||||
|
|
||||||
|
if (result.length === 0 || result[0].values.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [id, uname, role, banned, ipAddress, lastLogin, createdAt] = result[0].values[0];
|
||||||
|
return { id, username: uname, role, banned, ipAddress, lastLogin, createdAt };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновить пользователя
|
||||||
|
*/
|
||||||
|
function updateUser(username, updates) {
|
||||||
|
const user = getUserByUsername(username);
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('Пользователь не найден');
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = [];
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
if (updates.role !== undefined) {
|
||||||
|
fields.push('role = ?');
|
||||||
|
values.push(updates.role);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.banned !== undefined) {
|
||||||
|
fields.push('banned = ?');
|
||||||
|
values.push(updates.banned);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.password) {
|
||||||
|
const hashedPassword = bcrypt.hashSync(updates.password, 10);
|
||||||
|
fields.push('password_hash = ?');
|
||||||
|
values.push(hashedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.length === 0) {
|
||||||
|
return; // Нечего обновлять
|
||||||
|
}
|
||||||
|
|
||||||
|
values.push(username);
|
||||||
|
|
||||||
|
db.run(`
|
||||||
|
UPDATE users
|
||||||
|
SET ${fields.join(', ')}
|
||||||
|
WHERE username = ?
|
||||||
|
`, values);
|
||||||
|
|
||||||
|
saveDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удалить пользователя
|
||||||
|
*/
|
||||||
|
function deleteUser(username) {
|
||||||
|
db.run(`DELETE FROM users WHERE username = ?`, [username]);
|
||||||
|
db.run(`DELETE FROM user_settings WHERE user_id = (SELECT id FROM users WHERE username = ?)`, [username]);
|
||||||
|
saveDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// ЭКСПОРТ
|
// ЭКСПОРТ
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -580,6 +712,10 @@ module.exports = {
|
||||||
loginUser,
|
loginUser,
|
||||||
verifyToken,
|
verifyToken,
|
||||||
getUserById,
|
getUserById,
|
||||||
|
getAllUsers,
|
||||||
|
getUserByUsername,
|
||||||
|
updateUser,
|
||||||
|
deleteUser,
|
||||||
|
|
||||||
// Настройки
|
// Настройки
|
||||||
getUserSettings,
|
getUserSettings,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Texas Hold'em Poker - Скрипт развертывания
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🃏 Texas Hold'em Poker - Развертывание"
|
||||||
|
echo "======================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Проверка Docker
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
echo "❌ Docker не установлен!"
|
||||||
|
echo "Установите Docker: https://docs.docker.com/get-docker/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверка Docker Compose
|
||||||
|
if ! command -v docker-compose &> /dev/null; then
|
||||||
|
echo "❌ Docker Compose не установлен!"
|
||||||
|
echo "Установите Docker Compose: https://docs.docker.com/compose/install/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Docker и Docker Compose установлены"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Создание директорий
|
||||||
|
echo "📁 Создание необходимых директорий..."
|
||||||
|
mkdir -p data
|
||||||
|
mkdir -p logs
|
||||||
|
echo "✅ Директории созданы"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Остановка старого контейнера
|
||||||
|
echo "🛑 Остановка старых контейнеров..."
|
||||||
|
docker-compose down 2>/dev/null || true
|
||||||
|
echo "✅ Старые контейнеры остановлены"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Сборка образа
|
||||||
|
echo "🔨 Сборка Docker образа..."
|
||||||
|
docker-compose build
|
||||||
|
echo "✅ Образ собран"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Запуск контейнера
|
||||||
|
echo "🚀 Запуск контейнера..."
|
||||||
|
docker-compose up -d
|
||||||
|
echo "✅ Контейнер запущен"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Ожидание запуска
|
||||||
|
echo "⏳ Ожидание запуска сервера..."
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# Проверка статуса
|
||||||
|
if docker-compose ps | grep -q "Up"; then
|
||||||
|
echo "✅ Сервер успешно запущен!"
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "🎉 Развертывание завершено!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "📍 Сервер доступен по адресу:"
|
||||||
|
echo " http://localhost:3336"
|
||||||
|
echo ""
|
||||||
|
echo "📊 Полезные команды:"
|
||||||
|
echo " docker-compose logs -f # Просмотр логов"
|
||||||
|
echo " docker-compose ps # Статус контейнеров"
|
||||||
|
echo " docker-compose stop # Остановка"
|
||||||
|
echo " docker-compose restart # Перезапуск"
|
||||||
|
echo " docker-compose down # Остановка и удаление"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo "❌ Ошибка запуска сервера!"
|
||||||
|
echo "Проверьте логи: docker-compose logs"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
poker-app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: texas-holdem-poker
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3336:3336"
|
||||||
|
volumes:
|
||||||
|
# Монтируем директорию для сохранения базы данных
|
||||||
|
- ./data:/app/data
|
||||||
|
# Опционально: монтируем логи
|
||||||
|
- ./logs:/app/logs
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3336
|
||||||
|
networks:
|
||||||
|
- poker-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
poker-network:
|
||||||
|
driver: bridge
|
||||||
|
|
@ -777,8 +777,104 @@
|
||||||
|
|
||||||
<!-- Вкладка пользователей -->
|
<!-- Вкладка пользователей -->
|
||||||
<div id="admin-tab-users" class="admin-tab-content" style="display: none;">
|
<div id="admin-tab-users" class="admin-tab-content" style="display: none;">
|
||||||
<h3>👥 Пользователи</h3>
|
<h3>👥 Управление пользователями</h3>
|
||||||
<p style="color: var(--text-muted);">Раздел в разработке...</p>
|
|
||||||
|
<!-- Статистика -->
|
||||||
|
<div class="user-stats" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 20px;">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value" id="stat-total-users">0</div>
|
||||||
|
<div class="stat-label">Всего</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value" id="stat-active-users">0</div>
|
||||||
|
<div class="stat-label">Активных (сегодня)</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value" id="stat-banned-users">0</div>
|
||||||
|
<div class="stat-label">Заблокировано</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value" id="stat-admin-users">0</div>
|
||||||
|
<div class="stat-label">Админов</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Фильтры -->
|
||||||
|
<div class="user-filters" style="display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap;">
|
||||||
|
<select id="user-filter-role" class="input" onchange="loadUsers()" style="flex: 1; min-width: 150px;">
|
||||||
|
<option value="">Все роли</option>
|
||||||
|
<option value="admin">Админы</option>
|
||||||
|
<option value="user">Пользователи</option>
|
||||||
|
</select>
|
||||||
|
<select id="user-filter-status" class="input" onchange="loadUsers()" style="flex: 1; min-width: 150px;">
|
||||||
|
<option value="">Все статусы</option>
|
||||||
|
<option value="active">Активные</option>
|
||||||
|
<option value="banned">Заблокированные</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" id="user-search" class="input" placeholder="🔍 Поиск по логину..."
|
||||||
|
oninput="loadUsers()" style="flex: 2; min-width: 200px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Таблица пользователей -->
|
||||||
|
<div class="users-table-container" style="overflow-x: auto;">
|
||||||
|
<table class="users-table" style="width: 100%; border-collapse: collapse;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background: var(--bg-secondary); text-align: left;">
|
||||||
|
<th style="padding: 12px; border-bottom: 2px solid var(--border-color);">Логин</th>
|
||||||
|
<th style="padding: 12px; border-bottom: 2px solid var(--border-color);">Роль</th>
|
||||||
|
<th style="padding: 12px; border-bottom: 2px solid var(--border-color);">IP адрес</th>
|
||||||
|
<th style="padding: 12px; border-bottom: 2px solid var(--border-color);">Последний вход</th>
|
||||||
|
<th style="padding: 12px; border-bottom: 2px solid var(--border-color);">Статус</th>
|
||||||
|
<th style="padding: 12px; border-bottom: 2px solid var(--border-color); text-align: center;">Действия</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="users-table-body">
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" style="padding: 20px; text-align: center; color: var(--text-muted);">
|
||||||
|
Загрузка пользователей...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Модальное окно редактирования пользователя -->
|
||||||
|
<div id="edit-user-modal" class="modal" style="display: none;">
|
||||||
|
<div class="modal-content glass-container" style="max-width: 500px;">
|
||||||
|
<h3>✏️ Редактирование пользователя</h3>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Логин</label>
|
||||||
|
<input type="text" id="edit-user-username" class="input" readonly style="background: var(--bg-secondary);">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Роль</label>
|
||||||
|
<select id="edit-user-role" class="input">
|
||||||
|
<option value="user">👤 Пользователь</option>
|
||||||
|
<option value="admin">👑 Администратор</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Статус</label>
|
||||||
|
<select id="edit-user-status" class="input">
|
||||||
|
<option value="active">✅ Активен</option>
|
||||||
|
<option value="banned">🚫 Заблокирован</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Новый пароль (оставьте пустым, чтобы не менять)</label>
|
||||||
|
<input type="password" id="edit-user-password" class="input" placeholder="Новый пароль...">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 12px; margin-top: 20px;">
|
||||||
|
<button class="btn btn-success" onclick="saveUserEdit()">💾 Сохранить</button>
|
||||||
|
<button class="btn btn-secondary" onclick="closeEditUserModal()">❌ Отмена</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
391
public/main.js
391
public/main.js
|
|
@ -2943,3 +2943,394 @@ function initCustomPrompts() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ (АДМИН-ПАНЕЛЬ)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Переключение вкладок админ-панели
|
||||||
|
*/
|
||||||
|
function switchAdminTab(tabName) {
|
||||||
|
// Скрываем все вкладки
|
||||||
|
document.querySelectorAll('.admin-tab-content').forEach(tab => {
|
||||||
|
tab.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Убираем активный класс со всех кнопок
|
||||||
|
document.querySelectorAll('.btn-tab').forEach(btn => {
|
||||||
|
btn.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Показываем выбранную вкладку
|
||||||
|
const selectedTab = document.getElementById(`admin-tab-${tabName}`);
|
||||||
|
if (selectedTab) {
|
||||||
|
selectedTab.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Активируем кнопку
|
||||||
|
event.target.classList.add('active');
|
||||||
|
|
||||||
|
// Загружаем данные для вкладки
|
||||||
|
if (tabName === 'users') {
|
||||||
|
loadUsers();
|
||||||
|
} else if (tabName === 'logs') {
|
||||||
|
loadLogs();
|
||||||
|
} else if (tabName === 'bot-prompts') {
|
||||||
|
initBotPersonalitySelector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Загрузить список пользователей
|
||||||
|
*/
|
||||||
|
async function loadUsers() {
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('authToken');
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
showNotification('Необходима авторизация', 'error');
|
||||||
|
// Перезагружаем страницу для возврата на экран входа
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleFilter = document.getElementById('user-filter-role')?.value || '';
|
||||||
|
const statusFilter = document.getElementById('user-filter-status')?.value || '';
|
||||||
|
const search = document.getElementById('user-search')?.value || '';
|
||||||
|
|
||||||
|
const response = await fetch('/api/admin/users', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
role: roleFilter,
|
||||||
|
status: statusFilter,
|
||||||
|
search: search
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
showNotification('Сессия истекла. Войдите заново', 'error');
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
// Перезагружаем страницу для возврата на экран входа
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 403) {
|
||||||
|
showNotification('Доступ запрещён. Требуются права администратора', 'error');
|
||||||
|
// Показываем ошибку в таблице
|
||||||
|
const tbody = document.getElementById('users-table-body');
|
||||||
|
if (tbody) {
|
||||||
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" style="padding: 20px; text-align: center; color: var(--danger);">
|
||||||
|
🚫 Доступ запрещён. Требуются права администратора.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.error || 'Ошибка загрузки пользователей');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Обновляем статистику
|
||||||
|
updateUserStats(data.stats);
|
||||||
|
|
||||||
|
// Обновляем таблицу
|
||||||
|
renderUsersTable(data.users);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка загрузки пользователей:', error);
|
||||||
|
showNotification(error.message || 'Ошибка загрузки пользователей', 'error');
|
||||||
|
|
||||||
|
// Показываем заглушку в таблице
|
||||||
|
const tbody = document.getElementById('users-table-body');
|
||||||
|
if (tbody) {
|
||||||
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" style="padding: 20px; text-align: center; color: var(--danger);">
|
||||||
|
❌ ${error.message}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновить статистику пользователей
|
||||||
|
*/
|
||||||
|
function updateUserStats(stats) {
|
||||||
|
document.getElementById('stat-total-users').textContent = stats.total || 0;
|
||||||
|
document.getElementById('stat-active-users').textContent = stats.active || 0;
|
||||||
|
document.getElementById('stat-banned-users').textContent = stats.banned || 0;
|
||||||
|
document.getElementById('stat-admin-users').textContent = stats.admins || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отрисовать таблицу пользователей
|
||||||
|
*/
|
||||||
|
function renderUsersTable(users) {
|
||||||
|
const tbody = document.getElementById('users-table-body');
|
||||||
|
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" style="padding: 20px; text-align: center; color: var(--text-muted);">
|
||||||
|
Пользователи не найдены
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody.innerHTML = users.map(user => {
|
||||||
|
const roleClass = user.role === 'admin' ? 'user-role-admin' : 'user-role-user';
|
||||||
|
const roleText = user.role === 'admin' ? '👑 Админ' : '👤 Пользователь';
|
||||||
|
|
||||||
|
const statusClass = user.banned ? 'user-status-banned' : 'user-status-active';
|
||||||
|
const statusText = user.banned ? '🚫 Заблокирован' : '✅ Активен';
|
||||||
|
|
||||||
|
const lastSeen = user.lastLogin ? formatDate(user.lastLogin) : 'Никогда';
|
||||||
|
const ipAddress = user.ipAddress || 'Не известен';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<tr>
|
||||||
|
<td style="font-weight: 600;">
|
||||||
|
${user.username}
|
||||||
|
${user.username === currentUser?.username ? '<small style="color: var(--accent-primary);">(Вы)</small>' : ''}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="user-role-badge ${roleClass}">${roleText}</span>
|
||||||
|
</td>
|
||||||
|
<td style="font-family: monospace; font-size: 12px;">
|
||||||
|
${ipAddress}
|
||||||
|
</td>
|
||||||
|
<td style="color: var(--text-muted);">
|
||||||
|
${lastSeen}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="user-status-badge ${statusClass}">${statusText}</span>
|
||||||
|
</td>
|
||||||
|
<td class="user-actions">
|
||||||
|
<button class="btn btn-icon btn-primary" onclick='editUser(${JSON.stringify(user)})' title="Редактировать">
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
${user.banned ?
|
||||||
|
`<button class="btn btn-icon btn-success" onclick="toggleBanUser('${user.username}', false)" title="Разблокировать">
|
||||||
|
✅
|
||||||
|
</button>` :
|
||||||
|
`<button class="btn btn-icon btn-danger" onclick="toggleBanUser('${user.username}', true)" title="Заблокировать">
|
||||||
|
🚫
|
||||||
|
</button>`
|
||||||
|
}
|
||||||
|
${user.username !== currentUser?.username ?
|
||||||
|
`<button class="btn btn-icon btn-danger" onclick="deleteUser('${user.username}')" title="Удалить">
|
||||||
|
🗑️
|
||||||
|
</button>` : ''
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Форматировать дату
|
||||||
|
*/
|
||||||
|
function formatDate(dateString) {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now - date;
|
||||||
|
|
||||||
|
const minutes = Math.floor(diff / 60000);
|
||||||
|
const hours = Math.floor(diff / 3600000);
|
||||||
|
const days = Math.floor(diff / 86400000);
|
||||||
|
|
||||||
|
if (minutes < 1) return 'Только что';
|
||||||
|
if (minutes < 60) return `${minutes} мин назад`;
|
||||||
|
if (hours < 24) return `${hours} ч назад`;
|
||||||
|
if (days < 7) return `${days} дн назад`;
|
||||||
|
|
||||||
|
return date.toLocaleDateString('ru-RU', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Редактировать пользователя
|
||||||
|
*/
|
||||||
|
function editUser(user) {
|
||||||
|
document.getElementById('edit-user-username').value = user.username;
|
||||||
|
document.getElementById('edit-user-role').value = user.role;
|
||||||
|
document.getElementById('edit-user-status').value = user.banned ? 'banned' : 'active';
|
||||||
|
document.getElementById('edit-user-password').value = '';
|
||||||
|
|
||||||
|
document.getElementById('edit-user-modal').style.display = 'flex';
|
||||||
|
|
||||||
|
// Сохраняем данные для обновления
|
||||||
|
window.editingUser = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Закрыть модальное окно редактирования
|
||||||
|
*/
|
||||||
|
function closeEditUserModal() {
|
||||||
|
document.getElementById('edit-user-modal').style.display = 'none';
|
||||||
|
window.editingUser = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сохранить изменения пользователя
|
||||||
|
*/
|
||||||
|
async function saveUserEdit() {
|
||||||
|
if (!window.editingUser) return;
|
||||||
|
|
||||||
|
const username = window.editingUser.username;
|
||||||
|
const role = document.getElementById('edit-user-role').value;
|
||||||
|
const status = document.getElementById('edit-user-status').value;
|
||||||
|
const password = document.getElementById('edit-user-password').value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('authToken');
|
||||||
|
if (!token) {
|
||||||
|
showNotification('Необходима авторизация', 'error');
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/admin/user/update', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
role,
|
||||||
|
banned: status === 'banned',
|
||||||
|
password: password || undefined
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.error || 'Ошибка обновления пользователя');
|
||||||
|
}
|
||||||
|
|
||||||
|
showNotification('Пользователь успешно обновлён', 'success');
|
||||||
|
closeEditUserModal();
|
||||||
|
loadUsers();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка обновления пользователя:', error);
|
||||||
|
showNotification(error.message || 'Ошибка обновления пользователя', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Заблокировать/разблокировать пользователя
|
||||||
|
*/
|
||||||
|
async function toggleBanUser(username, ban) {
|
||||||
|
const action = ban ? 'заблокировать' : 'разблокировать';
|
||||||
|
|
||||||
|
if (!confirm(`Вы уверены, что хотите ${action} пользователя ${username}?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('authToken');
|
||||||
|
if (!token) {
|
||||||
|
showNotification('Необходима авторизация', 'error');
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/admin/user/ban', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username, banned: ban })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.error || 'Ошибка изменения статуса пользователя');
|
||||||
|
}
|
||||||
|
|
||||||
|
showNotification(`Пользователь ${username} ${ban ? 'заблокирован' : 'разблокирован'}`, 'success');
|
||||||
|
loadUsers();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка изменения статуса:', error);
|
||||||
|
showNotification(error.message || 'Ошибка изменения статуса пользователя', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удалить пользователя
|
||||||
|
*/
|
||||||
|
async function deleteUser(username) {
|
||||||
|
if (!confirm(`Вы уверены, что хотите УДАЛИТЬ пользователя ${username}?\n\nЭто действие необратимо!`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Двойное подтверждение для безопасности
|
||||||
|
if (!confirm(`ВНИМАНИЕ! Вы действительно хотите удалить ${username}?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('authToken');
|
||||||
|
if (!token) {
|
||||||
|
showNotification('Необходима авторизация', 'error');
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/admin/user/delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.error || 'Ошибка удаления пользователя');
|
||||||
|
}
|
||||||
|
|
||||||
|
showNotification(`Пользователь ${username} удалён`, 'success');
|
||||||
|
loadUsers();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка удаления:', error);
|
||||||
|
showNotification(error.message || 'Ошибка удаления пользователя', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Заглушка для функции загрузки логов
|
||||||
|
*/
|
||||||
|
function loadLogs() {
|
||||||
|
console.log('Загрузка логов...');
|
||||||
|
// Будет реализовано позже
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2422,3 +2422,153 @@ select.input {
|
||||||
.admin-tab-content {
|
.admin-tab-content {
|
||||||
animation: fadeInUp 0.3s ease;
|
animation: fadeInUp 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ (АДМИН-ПАНЕЛЬ)
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-table-container {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-table {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-table th {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-table tbody tr {
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-table tbody tr:hover {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-table td {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-role-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-role-admin {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-role-user {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-status-active {
|
||||||
|
background: rgba(16, 185, 129, 0.2);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-status-banned {
|
||||||
|
background: rgba(239, 68, 68, 0.2);
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
min-width: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Адаптивность таблицы */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.users-table {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-table th,
|
||||||
|
.users-table td {
|
||||||
|
padding: 8px 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.user-stats {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-table {
|
||||||
|
min-width: 800px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
160
server.js
160
server.js
|
|
@ -83,7 +83,10 @@ app.post('/api/auth/login', (req, res) => {
|
||||||
return res.status(400).json({ error: 'Введите логин и пароль' });
|
return res.status(400).json({ error: 'Введите логин и пароль' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = database.loginUser(username, password);
|
// Получаем IP адрес клиента
|
||||||
|
const ipAddress = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress;
|
||||||
|
|
||||||
|
const result = database.loginUser(username, password, ipAddress);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
res.json(result);
|
res.json(result);
|
||||||
|
|
@ -170,6 +173,161 @@ app.get('/api/logs/stats', authMiddleware, adminMiddleware, (req, res) => {
|
||||||
res.json({ stats });
|
res.json({ stats });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// REST API - УПРАВЛЕНИЕ ПОЛЬЗОВАТЕЛЯМИ (только для админа)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Получить список пользователей с фильтрами
|
||||||
|
app.post('/api/admin/users', authMiddleware, adminMiddleware, (req, res) => {
|
||||||
|
try {
|
||||||
|
const { role, status, search } = req.body;
|
||||||
|
|
||||||
|
// Получаем всех пользователей
|
||||||
|
const allUsers = database.getAllUsers();
|
||||||
|
|
||||||
|
// Применяем фильтры
|
||||||
|
let filteredUsers = allUsers;
|
||||||
|
|
||||||
|
if (role) {
|
||||||
|
filteredUsers = filteredUsers.filter(u => u.role === role);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'banned') {
|
||||||
|
filteredUsers = filteredUsers.filter(u => u.banned === 1);
|
||||||
|
} else if (status === 'active') {
|
||||||
|
filteredUsers = filteredUsers.filter(u => u.banned === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
const searchLower = search.toLowerCase();
|
||||||
|
filteredUsers = filteredUsers.filter(u =>
|
||||||
|
u.username.toLowerCase().includes(searchLower)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Статистика
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const stats = {
|
||||||
|
total: allUsers.length,
|
||||||
|
active: allUsers.filter(u => {
|
||||||
|
if (!u.lastLogin) return false;
|
||||||
|
const lastLogin = new Date(u.lastLogin);
|
||||||
|
return lastLogin >= today;
|
||||||
|
}).length,
|
||||||
|
banned: allUsers.filter(u => u.banned === 1).length,
|
||||||
|
admins: allUsers.filter(u => u.role === 'admin').length
|
||||||
|
};
|
||||||
|
|
||||||
|
// Логируем действие
|
||||||
|
database.logAction(req.user.userId, req.user.username, 'view_users', { count: filteredUsers.length });
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
users: filteredUsers,
|
||||||
|
stats
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка получения пользователей:', error);
|
||||||
|
res.status(500).json({ error: 'Ошибка сервера' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обновить пользователя
|
||||||
|
app.post('/api/admin/user/update', authMiddleware, adminMiddleware, (req, res) => {
|
||||||
|
try {
|
||||||
|
const { username, role, banned, password } = req.body;
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
return res.status(400).json({ error: 'Логин обязателен' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем пользователя
|
||||||
|
const user = database.getUserByUsername(username);
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ error: 'Пользователь не найден' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем данные
|
||||||
|
database.updateUser(username, {
|
||||||
|
role: role || user.role,
|
||||||
|
banned: banned ? 1 : 0,
|
||||||
|
password: password || undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
// Логируем действие
|
||||||
|
database.logAction(req.user.userId, req.user.username, 'update_user', {
|
||||||
|
target: username,
|
||||||
|
changes: { role, banned, passwordChanged: !!password }
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({ success: true });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка обновления пользователя:', error);
|
||||||
|
res.status(500).json({ error: 'Ошибка сервера' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Заблокировать/разблокировать пользователя
|
||||||
|
app.post('/api/admin/user/ban', authMiddleware, adminMiddleware, (req, res) => {
|
||||||
|
try {
|
||||||
|
const { username, banned } = req.body;
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
return res.status(400).json({ error: 'Логин обязателен' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Нельзя заблокировать себя
|
||||||
|
if (username === req.user.username) {
|
||||||
|
return res.status(403).json({ error: 'Нельзя заблокировать себя' });
|
||||||
|
}
|
||||||
|
|
||||||
|
database.updateUser(username, { banned: banned ? 1 : 0 });
|
||||||
|
|
||||||
|
// Логируем действие
|
||||||
|
database.logAction(req.user.userId, req.user.username, banned ? 'ban_user' : 'unban_user', {
|
||||||
|
target: username
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({ success: true });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка изменения статуса:', error);
|
||||||
|
res.status(500).json({ error: 'Ошибка сервера' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Удалить пользователя
|
||||||
|
app.post('/api/admin/user/delete', authMiddleware, adminMiddleware, (req, res) => {
|
||||||
|
try {
|
||||||
|
const { username } = req.body;
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
return res.status(400).json({ error: 'Логин обязателен' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Нельзя удалить себя
|
||||||
|
if (username === req.user.username) {
|
||||||
|
return res.status(403).json({ error: 'Нельзя удалить себя' });
|
||||||
|
}
|
||||||
|
|
||||||
|
database.deleteUser(username);
|
||||||
|
|
||||||
|
// Логируем действие
|
||||||
|
database.logAction(req.user.userId, req.user.username, 'delete_user', {
|
||||||
|
target: username
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({ success: true });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка удаления пользователя:', error);
|
||||||
|
res.status(500).json({ error: 'Ошибка сервера' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// REST API - ЧАТ
|
// REST API - ЧАТ
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue