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
|
||||
146
database.js
146
database.js
|
|
@ -57,6 +57,8 @@ function createTables() {
|
|||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT DEFAULT 'user',
|
||||
banned INTEGER DEFAULT 0,
|
||||
ip_address TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login TEXT,
|
||||
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(`
|
||||
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 {
|
||||
const result = db.exec(`
|
||||
SELECT id, username, password_hash, role
|
||||
SELECT id, username, password_hash, role, banned
|
||||
FROM users
|
||||
WHERE username = ?
|
||||
`, [username]);
|
||||
|
|
@ -258,20 +278,31 @@ function loginUser(username, password) {
|
|||
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)) {
|
||||
return { success: false, error: 'Неверный логин или пароль' };
|
||||
}
|
||||
|
||||
// Обновляем last_login
|
||||
// Обновляем last_login и IP
|
||||
if (ipAddress) {
|
||||
db.run(`
|
||||
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();
|
||||
|
||||
// Логируем
|
||||
logAction(userId, storedUsername, 'login', { username: storedUsername });
|
||||
logAction(userId, storedUsername, 'login', { username: storedUsername, ip: ipAddress });
|
||||
|
||||
// Генерируем токен
|
||||
const token = generateToken(userId, storedUsername, role);
|
||||
|
|
@ -567,6 +598,107 @@ function clearRoomChat(roomId) {
|
|||
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,
|
||||
verifyToken,
|
||||
getUserById,
|
||||
getAllUsers,
|
||||
getUserByUsername,
|
||||
updateUser,
|
||||
deleteUser,
|
||||
|
||||
// Настройки
|
||||
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;">
|
||||
<h3>👥 Пользователи</h3>
|
||||
<p style="color: var(--text-muted);">Раздел в разработке...</p>
|
||||
<h3>👥 Управление пользователями</h3>
|
||||
|
||||
<!-- Статистика -->
|
||||
<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>
|
||||
|
|
|
|||
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 {
|
||||
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: 'Введите логин и пароль' });
|
||||
}
|
||||
|
||||
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) {
|
||||
res.json(result);
|
||||
|
|
@ -170,6 +173,161 @@ app.get('/api/logs/stats', authMiddleware, adminMiddleware, (req, res) => {
|
|||
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 - ЧАТ
|
||||
// =============================================================================
|
||||
|
|
|
|||
Loading…
Reference in New Issue