Add Notification and new mini desing
This commit is contained in:
531
API.md
Normal file
531
API.md
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
# MC Panel API - Полная документация
|
||||||
|
|
||||||
|
**Версия:** 1.0.0
|
||||||
|
**Дата:** 15 января 2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Содержание
|
||||||
|
|
||||||
|
1. [Базовая информация](#базовая-информация)
|
||||||
|
2. [Быстрый старт](#быстрый-старт)
|
||||||
|
3. [Аутентификация](#аутентификация)
|
||||||
|
4. [Управление пользователями](#управление-пользователями)
|
||||||
|
5. [Личный кабинет](#личный-кабинет)
|
||||||
|
6. [Управление серверами](#управление-серверами)
|
||||||
|
7. [Управление файлами](#управление-файлами)
|
||||||
|
8. [Тикеты](#тикеты)
|
||||||
|
9. [OpenID Connect](#openid-connect)
|
||||||
|
10. [Коды ошибок](#коды-ошибок)
|
||||||
|
11. [Примеры интеграции](#примеры-интеграции)
|
||||||
|
12. [Postman коллекция](#postman-коллекция)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Базовая информация
|
||||||
|
|
||||||
|
**Base URL:** `http://localhost:8000`
|
||||||
|
|
||||||
|
**Формат данных:** JSON
|
||||||
|
|
||||||
|
**Аутентификация:** Bearer Token (JWT)
|
||||||
|
|
||||||
|
Все защищенные эндпоинты требуют заголовок:
|
||||||
|
```
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Заголовки запросов
|
||||||
|
```
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Формат ответов
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Success message",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
### 1. Регистрация
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8000/api/auth/register \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"password123"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Вход
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8000/api/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"password123"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "eyJhbGc...",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"username": "admin",
|
||||||
|
"role": "admin"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Использование токена
|
||||||
|
```bash
|
||||||
|
TOKEN="your_token_here"
|
||||||
|
|
||||||
|
curl http://localhost:8000/api/servers \
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Аутентификация
|
||||||
|
|
||||||
|
### POST /api/auth/register
|
||||||
|
Регистрация нового пользователя.
|
||||||
|
|
||||||
|
**Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "string",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"username": "string",
|
||||||
|
"role": "admin|user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### POST /api/auth/login
|
||||||
|
Вход в систему.
|
||||||
|
|
||||||
|
**Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "string",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"username": "string",
|
||||||
|
"role": "admin|user|support|banned"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Errors:**
|
||||||
|
- `401` - Неверные учетные данные
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### GET /api/auth/me
|
||||||
|
Получить информацию о текущем пользователе.
|
||||||
|
|
||||||
|
**Headers:** `Authorization: Bearer <token>`
|
||||||
|
|
||||||
|
**Response (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "string",
|
||||||
|
"role": "admin|user|support|banned",
|
||||||
|
"servers": ["server1", "server2"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Управление пользователями
|
||||||
|
|
||||||
|
### GET /api/users
|
||||||
|
Список всех пользователей.
|
||||||
|
|
||||||
|
### PUT /api/users/{username}/role
|
||||||
|
Изменить роль пользователя (admin only).
|
||||||
|
**Body:** `{"role": "admin|user|support|banned"}`
|
||||||
|
|
||||||
|
### PUT /api/users/{username}/servers
|
||||||
|
Управление доступом к серверам.
|
||||||
|
**Body:** `{"servers": ["server1", "server2"]}`
|
||||||
|
|
||||||
|
### DELETE /api/users/{username}
|
||||||
|
Удалить пользователя (admin only).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Личный кабинет
|
||||||
|
|
||||||
|
### GET /api/profile/stats
|
||||||
|
Статистика текущего пользователя.
|
||||||
|
|
||||||
|
### GET /api/profile/stats/{username}
|
||||||
|
Статистика другого пользователя (admin/support).
|
||||||
|
|
||||||
|
### PUT /api/profile/username
|
||||||
|
Изменить имя пользователя.
|
||||||
|
**Body:** `{"new_username": "string", "password": "string"}`
|
||||||
|
|
||||||
|
### PUT /api/profile/password
|
||||||
|
Изменить пароль.
|
||||||
|
**Body:** `{"old_password": "string", "new_password": "string"}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Управление серверами
|
||||||
|
|
||||||
|
### GET /api/servers
|
||||||
|
Список серверов пользователя.
|
||||||
|
|
||||||
|
### POST /api/servers/create
|
||||||
|
Создать новый сервер.
|
||||||
|
**Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "server1",
|
||||||
|
"displayName": "My Server",
|
||||||
|
"startCommand": "java -Xmx2G -jar server.jar nogui"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /api/servers/{server}/config
|
||||||
|
Получить конфигурацию сервера.
|
||||||
|
|
||||||
|
### PUT /api/servers/{server}/config
|
||||||
|
Обновить конфигурацию сервера.
|
||||||
|
|
||||||
|
### DELETE /api/servers/{server}
|
||||||
|
Удалить сервер (admin only).
|
||||||
|
|
||||||
|
### POST /api/servers/{server}/start
|
||||||
|
Запустить сервер.
|
||||||
|
|
||||||
|
### POST /api/servers/{server}/stop
|
||||||
|
Остановить сервер.
|
||||||
|
|
||||||
|
### POST /api/servers/{server}/command
|
||||||
|
Отправить команду серверу.
|
||||||
|
**Body:** `{"command": "say Hello"}`
|
||||||
|
|
||||||
|
### GET /api/servers/{server}/stats
|
||||||
|
Получить статистику сервера (CPU, RAM, Disk).
|
||||||
|
|
||||||
|
### WS /ws/servers/{server}/console
|
||||||
|
WebSocket для консоли сервера (логи в реальном времени).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Управление файлами
|
||||||
|
|
||||||
|
### GET /api/servers/{server}/files?path={path}
|
||||||
|
Список файлов в директории.
|
||||||
|
|
||||||
|
### POST /api/servers/{server}/files/create
|
||||||
|
Создать файл или папку.
|
||||||
|
**Body:** `{"type": "file|folder", "name": "string", "path": "string"}`
|
||||||
|
|
||||||
|
### POST /api/servers/{server}/files/upload?path={path}
|
||||||
|
Загрузить файл (multipart/form-data).
|
||||||
|
|
||||||
|
### GET /api/servers/{server}/files/download?path={path}
|
||||||
|
Скачать файл.
|
||||||
|
|
||||||
|
### GET /api/servers/{server}/files/content?path={path}
|
||||||
|
Получить содержимое текстового файла.
|
||||||
|
|
||||||
|
### PUT /api/servers/{server}/files/content?path={path}
|
||||||
|
Сохранить содержимое файла.
|
||||||
|
**Body:** `{"content": "string"}`
|
||||||
|
|
||||||
|
### PUT /api/servers/{server}/files/rename?old_path={path}&new_name={name}
|
||||||
|
Переименовать файл.
|
||||||
|
|
||||||
|
### POST /api/servers/{server}/files/move
|
||||||
|
Переместить файл.
|
||||||
|
**Body:** `{"source": "path", "destination": "path"}`
|
||||||
|
|
||||||
|
### DELETE /api/servers/{server}/files?path={path}
|
||||||
|
Удалить файл или папку.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Тикеты
|
||||||
|
|
||||||
|
### GET /api/tickets
|
||||||
|
Список тикетов (свои или все для admin/support).
|
||||||
|
|
||||||
|
### POST /api/tickets/create
|
||||||
|
Создать новый тикет.
|
||||||
|
**Body:** `{"title": "string", "description": "string"}`
|
||||||
|
|
||||||
|
### GET /api/tickets/{id}
|
||||||
|
Получить тикет по ID.
|
||||||
|
|
||||||
|
### POST /api/tickets/{id}/message
|
||||||
|
Добавить сообщение в тикет.
|
||||||
|
**Body:** `{"text": "string"}`
|
||||||
|
|
||||||
|
### PUT /api/tickets/{id}/status
|
||||||
|
Изменить статус тикета (admin/support).
|
||||||
|
**Body:** `{"status": "pending|in_progress|closed"}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenID Connect
|
||||||
|
|
||||||
|
### GET /api/auth/oidc/providers
|
||||||
|
Список доступных OIDC провайдеров.
|
||||||
|
|
||||||
|
### GET /api/auth/oidc/{provider}/login
|
||||||
|
Начать аутентификацию через OIDC (redirect).
|
||||||
|
|
||||||
|
### GET /api/auth/oidc/{provider}/callback
|
||||||
|
Callback от OIDC провайдера (redirect).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Коды ошибок
|
||||||
|
|
||||||
|
| Код | Описание | Решение |
|
||||||
|
|-----|----------|---------|
|
||||||
|
| 200 | Успешно | - |
|
||||||
|
| 400 | Неверный запрос | Проверьте формат данных |
|
||||||
|
| 401 | Не авторизован | Войдите в систему |
|
||||||
|
| 403 | Доступ запрещен | Недостаточно прав |
|
||||||
|
| 404 | Не найдено | Проверьте URL |
|
||||||
|
| 500 | Ошибка сервера | Обратитесь к администратору |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Примеры интеграции
|
||||||
|
|
||||||
|
### Python
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class MCPanelAPI:
|
||||||
|
def __init__(self, base_url, username, password):
|
||||||
|
self.base_url = base_url
|
||||||
|
self.token = None
|
||||||
|
self.login(username, password)
|
||||||
|
|
||||||
|
def login(self, username, password):
|
||||||
|
r = requests.post(f"{self.base_url}/api/auth/login",
|
||||||
|
json={"username": username, "password": password})
|
||||||
|
self.token = r.json()["access_token"]
|
||||||
|
|
||||||
|
def get_headers(self):
|
||||||
|
return {"Authorization": f"Bearer {self.token}"}
|
||||||
|
|
||||||
|
def get_servers(self):
|
||||||
|
r = requests.get(f"{self.base_url}/api/servers",
|
||||||
|
headers=self.get_headers())
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def start_server(self, server_name):
|
||||||
|
r = requests.post(
|
||||||
|
f"{self.base_url}/api/servers/{server_name}/start",
|
||||||
|
headers=self.get_headers())
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
# Использование
|
||||||
|
api = MCPanelAPI("http://localhost:8000", "admin", "password")
|
||||||
|
servers = api.get_servers()
|
||||||
|
api.start_server("survival")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### JavaScript
|
||||||
|
```javascript
|
||||||
|
class MCPanelAPI {
|
||||||
|
constructor(baseURL) {
|
||||||
|
this.baseURL = baseURL;
|
||||||
|
this.token = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(username, password) {
|
||||||
|
const response = await fetch(`${this.baseURL}/api/auth/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({username, password})
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
this.token = data.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeaders() {
|
||||||
|
return {
|
||||||
|
'Authorization': `Bearer ${this.token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getServers() {
|
||||||
|
const response = await fetch(`${this.baseURL}/api/servers`, {
|
||||||
|
headers: this.getHeaders()
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async startServer(serverName) {
|
||||||
|
const response = await fetch(
|
||||||
|
`${this.baseURL}/api/servers/${serverName}/start`,
|
||||||
|
{method: 'POST', headers: this.getHeaders()}
|
||||||
|
);
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Использование
|
||||||
|
const api = new MCPanelAPI('http://localhost:8000');
|
||||||
|
await api.login('admin', 'password');
|
||||||
|
const servers = await api.getServers();
|
||||||
|
await api.startServer('survival');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### cURL примеры
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Вход
|
||||||
|
TOKEN=$(curl -s -X POST http://localhost:8000/api/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"pass"}' \
|
||||||
|
| jq -r '.access_token')
|
||||||
|
|
||||||
|
# Список серверов
|
||||||
|
curl http://localhost:8000/api/servers \
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
|
||||||
|
# Создать сервер
|
||||||
|
curl -X POST http://localhost:8000/api/servers/create \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"survival","displayName":"Survival","startCommand":"java -jar server.jar"}'
|
||||||
|
|
||||||
|
# Запустить сервер
|
||||||
|
curl -X POST http://localhost:8000/api/servers/survival/start \
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
|
||||||
|
# Отправить команду
|
||||||
|
curl -X POST http://localhost:8000/api/servers/survival/command \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"command":"say Hello"}'
|
||||||
|
|
||||||
|
# Список файлов
|
||||||
|
curl "http://localhost:8000/api/servers/survival/files?path=plugins" \
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
|
||||||
|
# Создать тикет
|
||||||
|
curl -X POST http://localhost:8000/api/tickets/create \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"title":"Problem","description":"Details"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Postman коллекция
|
||||||
|
|
||||||
|
### Импорт коллекции
|
||||||
|
1. Откройте Postman
|
||||||
|
2. File → Import
|
||||||
|
3. Выберите файл `MC_Panel_API.postman_collection.json`
|
||||||
|
4. Коллекция готова к использованию
|
||||||
|
|
||||||
|
### Настройка переменных
|
||||||
|
В коллекции настройте переменные:
|
||||||
|
- `baseUrl` = `http://localhost:8000`
|
||||||
|
- `serverName` = `survival` (или имя вашего сервера)
|
||||||
|
- `token` = автоматически сохраняется после Login
|
||||||
|
|
||||||
|
### Использование
|
||||||
|
1. Выполните запрос "Login" для получения токена
|
||||||
|
2. Токен автоматически сохранится в переменную `token`
|
||||||
|
3. Все остальные запросы будут использовать этот токен
|
||||||
|
4. Используйте любые эндпоинты из коллекции
|
||||||
|
|
||||||
|
### Структура коллекции
|
||||||
|
- **Authentication** - регистрация, вход, получение пользователя
|
||||||
|
- **Users** - управление пользователями
|
||||||
|
- **Servers** - управление серверами
|
||||||
|
- **Files** - операции с файлами
|
||||||
|
- **Tickets** - система тикетов
|
||||||
|
- **Profile** - личный кабинет
|
||||||
|
- **OpenID Connect** - OIDC провайдеры
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
### JWT Токены
|
||||||
|
- Срок действия: 7 дней
|
||||||
|
- Алгоритм: HS256
|
||||||
|
- Хранение: localStorage (фронтенд)
|
||||||
|
|
||||||
|
### Рекомендации
|
||||||
|
1. Используйте HTTPS в production
|
||||||
|
2. Измените SECRET_KEY в `backend/main.py`
|
||||||
|
3. Используйте сильные пароли (минимум 6 символов)
|
||||||
|
4. Регулярно обновляйте зависимости
|
||||||
|
5. Ограничьте CORS для конкретных доменов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Лимиты и ограничения
|
||||||
|
|
||||||
|
- **Размер файла:** не ограничен (зависит от сервера)
|
||||||
|
- **Количество запросов:** не ограничено
|
||||||
|
- **Длина сообщения:** не ограничена
|
||||||
|
- **Количество серверов:** не ограничено
|
||||||
|
- **Срок хранения логов:** 1000 последних строк
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### 1.0.0 (15.01.2026)
|
||||||
|
- ✨ Первый релиз API
|
||||||
|
- ✅ 37 эндпоинтов
|
||||||
|
- ✅ JWT аутентификация
|
||||||
|
- ✅ OpenID Connect
|
||||||
|
- ✅ WebSocket консоль
|
||||||
|
- ✅ Полное управление серверами
|
||||||
|
- ✅ Файловый менеджер
|
||||||
|
- ✅ Система тикетов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Поддержка
|
||||||
|
|
||||||
|
- **Документация проекта:** ДОКУМЕНТАЦИЯ.md
|
||||||
|
- **Postman коллекция:** MC_Panel_API.postman_collection.json
|
||||||
|
- **Тикеты:** Используйте систему тикетов в панели
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Версия API:** 1.0.0
|
||||||
|
**Дата обновления:** 15 января 2026
|
||||||
|
|
||||||
|
**Спасибо за использование MC Panel API!** 🚀
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Применение исправлений
|
|
||||||
|
|
||||||
## Что исправлено
|
|
||||||
|
|
||||||
### ✅ Фронтенд компоненты
|
|
||||||
Все компоненты обновлены для передачи токена:
|
|
||||||
- `Console.jsx` ✅
|
|
||||||
- `Stats.jsx` ✅
|
|
||||||
- `FileManager.jsx` ✅
|
|
||||||
- `ServerSettings.jsx` ✅
|
|
||||||
- `CreateServerModal.jsx` ✅
|
|
||||||
|
|
||||||
### ⚠️ Нужно обновить вручную
|
|
||||||
|
|
||||||
#### 1. App.jsx (или App_final.jsx)
|
|
||||||
|
|
||||||
Найдите строку:
|
|
||||||
```jsx
|
|
||||||
{user?.role === 'admin' && (
|
|
||||||
<button
|
|
||||||
onClick={() => setShowCreateModal(true)}
|
|
||||||
className="bg-blue-600 hover:bg-blue-700 p-2 rounded"
|
|
||||||
title="Создать сервер"
|
|
||||||
>
|
|
||||||
<Plus className="w-4
|
|
||||||
196
AUTH_SETUP.md
196
AUTH_SETUP.md
@@ -1,196 +0,0 @@
|
|||||||
# Настройка системы авторизации
|
|
||||||
|
|
||||||
## Что добавлено
|
|
||||||
|
|
||||||
1. **Система авторизации** - вход и регистрация пользователей
|
|
||||||
2. **Роли пользователей** - администраторы и обычные пользователи
|
|
||||||
3. **Управление доступом** - админы могут выдавать доступ к серверам
|
|
||||||
4. **JWT токены** - безопасная авторизация
|
|
||||||
|
|
||||||
## Первый запуск
|
|
||||||
|
|
||||||
### 1. Установите новые зависимости
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Новые библиотеки:
|
|
||||||
- `passlib[bcrypt]` - хеширование паролей
|
|
||||||
- `python-jose[cryptography]` - JWT токены
|
|
||||||
|
|
||||||
### 2. Переименуйте файл бэкенда
|
|
||||||
|
|
||||||
**ВАЖНО:** Удалите старый `backend/main.py` и переименуйте `backend/main_new.py` в `backend/main.py`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
del main.py
|
|
||||||
ren main_new.py main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Или вручную в проводнике Windows.
|
|
||||||
|
|
||||||
### 3. Запустите бэкенд
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
При первом запуске создастся пользователь по умолчанию:
|
|
||||||
- **Логин:** admin
|
|
||||||
- **Пароль:** admin
|
|
||||||
|
|
||||||
### 4. Запустите фронтенд
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
### Первый вход
|
|
||||||
|
|
||||||
1. Откройте http://localhost:3000
|
|
||||||
2. Войдите как `admin` / `admin`
|
|
||||||
3. **ВАЖНО:** Смените пароль администратора!
|
|
||||||
|
|
||||||
### Создание пользователей
|
|
||||||
|
|
||||||
1. Нажмите кнопку **"Пользователи"** в шапке
|
|
||||||
2. Новые пользователи могут зарегистрироваться самостоятельно
|
|
||||||
3. По умолчанию новые пользователи получают роль "Пользователь"
|
|
||||||
|
|
||||||
### Управление доступом к серверам
|
|
||||||
|
|
||||||
1. Перейдите в **"Пользователи"**
|
|
||||||
2. Найдите нужного пользователя
|
|
||||||
3. Нажмите на названия серверов чтобы выдать/забрать доступ
|
|
||||||
4. Зеленые кнопки = доступ есть
|
|
||||||
5. Серые кнопки = доступа нет
|
|
||||||
|
|
||||||
### Роли пользователей
|
|
||||||
|
|
||||||
**Администратор:**
|
|
||||||
- Видит все серверы
|
|
||||||
- Может создавать/удалять серверы
|
|
||||||
- Может управлять пользователями
|
|
||||||
- Может изменять настройки серверов
|
|
||||||
|
|
||||||
**Пользователь:**
|
|
||||||
- Видит только серверы с доступом
|
|
||||||
- Может запускать/останавливать свои серверы
|
|
||||||
- Может управлять файлами своих серверов
|
|
||||||
- Не может создавать серверы
|
|
||||||
- Не может изменять настройки
|
|
||||||
|
|
||||||
### Изменение роли
|
|
||||||
|
|
||||||
1. Перейдите в **"Пользователи"**
|
|
||||||
2. Нажмите **"Сделать админом"** или **"Сделать пользователем"**
|
|
||||||
3. Подтвердите действие
|
|
||||||
|
|
||||||
### Удаление пользователя
|
|
||||||
|
|
||||||
1. Перейдите в **"Пользователи"**
|
|
||||||
2. Нажмите кнопку с иконкой корзины
|
|
||||||
3. Подтвердите удаление
|
|
||||||
|
|
||||||
**Примечание:** Нельзя удалить самого себя или изменить свою роль.
|
|
||||||
|
|
||||||
## Безопасность
|
|
||||||
|
|
||||||
### Смена секретного ключа
|
|
||||||
|
|
||||||
Откройте `backend/main_new.py` (или `main.py` после переименования) и измените:
|
|
||||||
|
|
||||||
```python
|
|
||||||
SECRET_KEY = "your-secret-key-change-this-in-production-12345"
|
|
||||||
```
|
|
||||||
|
|
||||||
На случайную строку, например:
|
|
||||||
```python
|
|
||||||
SECRET_KEY = "super-secret-key-" + str(uuid.uuid4())
|
|
||||||
```
|
|
||||||
|
|
||||||
### Время жизни токена
|
|
||||||
|
|
||||||
По умолчанию токен действует 7 дней. Чтобы изменить:
|
|
||||||
|
|
||||||
```python
|
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 дней
|
|
||||||
```
|
|
||||||
|
|
||||||
### Хранение паролей
|
|
||||||
|
|
||||||
Пароли хешируются с помощью bcrypt и хранятся в файле `backend/users.json`.
|
|
||||||
|
|
||||||
**Не удаляйте этот файл!** Иначе потеряете всех пользователей.
|
|
||||||
|
|
||||||
## Файлы
|
|
||||||
|
|
||||||
- `backend/users.json` - база пользователей
|
|
||||||
- `backend/main_new.py` - новый бэкенд с авторизацией
|
|
||||||
- `frontend/src/components/Auth.jsx` - форма входа/регистрации
|
|
||||||
- `frontend/src/components/Users.jsx` - управление пользователями
|
|
||||||
|
|
||||||
## API эндпоинты
|
|
||||||
|
|
||||||
### Авторизация
|
|
||||||
|
|
||||||
- `POST /api/auth/register` - регистрация
|
|
||||||
- `POST /api/auth/login` - вход
|
|
||||||
- `GET /api/auth/me` - информация о текущем пользователе
|
|
||||||
|
|
||||||
### Пользователи (только админы)
|
|
||||||
|
|
||||||
- `GET /api/users` - список пользователей
|
|
||||||
- `PUT /api/users/{username}/servers` - изменить доступ к серверам
|
|
||||||
- `PUT /api/users/{username}/role` - изменить роль
|
|
||||||
- `DELETE /api/users/{username}` - удалить пользователя
|
|
||||||
|
|
||||||
### Серверы (с проверкой доступа)
|
|
||||||
|
|
||||||
Все существующие эндпоинты теперь требуют токен в заголовке:
|
|
||||||
```
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Ошибка "Требуется авторизация"
|
|
||||||
|
|
||||||
Токен истек или недействителен. Выйдите и войдите заново.
|
|
||||||
|
|
||||||
### Не могу войти как admin
|
|
||||||
|
|
||||||
Удалите файл `backend/users.json` и перезапустите бэкенд. Создастся новый админ с паролем `admin`.
|
|
||||||
|
|
||||||
### Забыл пароль
|
|
||||||
|
|
||||||
Удалите файл `backend/users.json` - все пользователи будут удалены и создастся новый админ.
|
|
||||||
|
|
||||||
Или отредактируйте `users.json` вручную, удалив нужного пользователя.
|
|
||||||
|
|
||||||
### Пользователь не видит серверы
|
|
||||||
|
|
||||||
Проверьте что админ выдал ему доступ в разделе "Пользователи".
|
|
||||||
|
|
||||||
## Миграция со старой версии
|
|
||||||
|
|
||||||
Если у вас уже есть серверы:
|
|
||||||
|
|
||||||
1. Сделайте backup папки `backend/servers/`
|
|
||||||
2. Установите новые зависимости
|
|
||||||
3. Замените `main.py` на новый
|
|
||||||
4. Запустите бэкенд
|
|
||||||
5. Войдите как admin/admin
|
|
||||||
6. Все серверы будут доступны админу автоматически
|
|
||||||
7. Создайте пользователей и выдайте им доступ
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Готово!** Теперь у вас есть полноценная система авторизации и управления доступом! 🔐
|
|
||||||
176
BANNED_ROLE.md
176
BANNED_ROLE.md
@@ -1,176 +0,0 @@
|
|||||||
# ⛔ Роль "Забанен"
|
|
||||||
|
|
||||||
## Что добавлено
|
|
||||||
|
|
||||||
### Новая роль "Забанен" (banned)
|
|
||||||
Роль для блокировки пользователей, которые нарушили правила или должны быть временно/постоянно отстранены от использования панели.
|
|
||||||
|
|
||||||
## 🚫 Ограничения роли
|
|
||||||
|
|
||||||
### Полная блокировка доступа
|
|
||||||
Пользователи с ролью "Забанен" **не имеют доступа** ни к каким функциям панели:
|
|
||||||
|
|
||||||
- ❌ Создание серверов
|
|
||||||
- ❌ Управление серверами
|
|
||||||
- ❌ Просмотр консоли
|
|
||||||
- ❌ Менеджер файлов
|
|
||||||
- ❌ Создание тикетов
|
|
||||||
- ❌ Просмотр тикетов
|
|
||||||
- ❌ Личный кабинет
|
|
||||||
- ❌ Любые другие функции
|
|
||||||
|
|
||||||
### Что происходит при попытке входа
|
|
||||||
При попытке доступа к любому endpoint API пользователь получает ошибку:
|
|
||||||
```
|
|
||||||
403 Forbidden: "Ваш аккаунт заблокирован"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎨 Визуальное отображение
|
|
||||||
|
|
||||||
### В списке пользователей (Users.jsx)
|
|
||||||
- 🔴 **Красная иконка** пользователя
|
|
||||||
- **Текст роли**: "Забанен"
|
|
||||||
- **Описание**: "⛔ Пользователь заблокирован и не имеет доступа к панели"
|
|
||||||
|
|
||||||
### В личном кабинете (Profile.jsx)
|
|
||||||
- 🔴 **Красный бейдж** с текстом "Забанен"
|
|
||||||
- **Описание**: "⛔ Аккаунт заблокирован, доступ запрещён"
|
|
||||||
|
|
||||||
### В header (App.jsx)
|
|
||||||
- **Бейдж**: "Забанен" (красный цвет)
|
|
||||||
|
|
||||||
## 🔧 Как использовать
|
|
||||||
|
|
||||||
### Заблокировать пользователя
|
|
||||||
1. Войдите как администратор (none / none)
|
|
||||||
2. Нажмите кнопку "Пользователи" в header
|
|
||||||
3. Найдите нужного пользователя
|
|
||||||
4. В выпадающем списке выберите "Забанен"
|
|
||||||
5. Роль изменится автоматически
|
|
||||||
|
|
||||||
### Разблокировать пользователя
|
|
||||||
1. Войдите как администратор
|
|
||||||
2. Нажмите кнопку "Пользователи"
|
|
||||||
3. Найдите заблокированного пользователя
|
|
||||||
4. В выпадающем списке выберите другую роль:
|
|
||||||
- "Пользователь" - обычный доступ
|
|
||||||
- "Тех. поддержка" - доступ к тикетам
|
|
||||||
- "Администратор" - полный доступ
|
|
||||||
|
|
||||||
## 📋 Технические детали
|
|
||||||
|
|
||||||
### Backend (main.py)
|
|
||||||
|
|
||||||
#### Проверка в get_current_user()
|
|
||||||
```python
|
|
||||||
# Проверка на бан
|
|
||||||
if user.get("role") == "banned":
|
|
||||||
raise HTTPException(status_code=403, detail="Ваш аккаунт заблокирован")
|
|
||||||
```
|
|
||||||
|
|
||||||
Эта проверка выполняется **перед каждым запросом** к API, что гарантирует полную блокировку доступа.
|
|
||||||
|
|
||||||
#### Обновление роли
|
|
||||||
```python
|
|
||||||
new_role = data.get("role")
|
|
||||||
if new_role not in ["admin", "user", "support", "banned"]:
|
|
||||||
raise HTTPException(400, "Неверная роль")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
#### App.jsx
|
|
||||||
```javascript
|
|
||||||
const getRoleName = (role) => {
|
|
||||||
switch (role) {
|
|
||||||
case 'admin':
|
|
||||||
return 'Админ';
|
|
||||||
case 'support':
|
|
||||||
return 'Поддержка';
|
|
||||||
case 'banned':
|
|
||||||
return 'Забанен';
|
|
||||||
default:
|
|
||||||
return 'Пользователь';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Users.jsx
|
|
||||||
```javascript
|
|
||||||
<option value="banned">Забанен</option>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Profile.jsx
|
|
||||||
```javascript
|
|
||||||
case 'banned':
|
|
||||||
return 'bg-red-500/20 text-red-500 border-red-500/50';
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔐 Безопасность
|
|
||||||
|
|
||||||
### Защита на уровне API
|
|
||||||
- Проверка роли выполняется в функции `get_current_user()`
|
|
||||||
- Блокировка происходит **до** выполнения любого endpoint
|
|
||||||
- Невозможно обойти блокировку через API
|
|
||||||
|
|
||||||
### Защита на уровне UI
|
|
||||||
- Визуальное отображение статуса блокировки
|
|
||||||
- Красные индикаторы для предупреждения
|
|
||||||
- Понятные сообщения об ошибках
|
|
||||||
|
|
||||||
## ⚠️ Важные замечания
|
|
||||||
|
|
||||||
### Администраторы не могут быть заблокированы
|
|
||||||
Рекомендуется добавить проверку, чтобы администраторы не могли заблокировать сами себя или других администраторов.
|
|
||||||
|
|
||||||
### Логирование блокировок
|
|
||||||
Рекомендуется добавить логирование:
|
|
||||||
- Кто заблокировал пользователя
|
|
||||||
- Когда была выполнена блокировка
|
|
||||||
- Причина блокировки (опционально)
|
|
||||||
|
|
||||||
### Уведомления
|
|
||||||
Можно добавить:
|
|
||||||
- Email уведомление о блокировке
|
|
||||||
- Причину блокировки в профиле
|
|
||||||
- Дату окончания блокировки (для временных банов)
|
|
||||||
|
|
||||||
## 📊 Статистика
|
|
||||||
|
|
||||||
### Роли в системе
|
|
||||||
1. **Администратор** (admin) - полный доступ
|
|
||||||
2. **Тех. поддержка** (support) - доступ к тикетам
|
|
||||||
3. **Пользователь** (user) - доступ к своим серверам
|
|
||||||
4. **Забанен** (banned) - нет доступа ⛔
|
|
||||||
|
|
||||||
## ✅ Готово!
|
|
||||||
|
|
||||||
Роль "Забанен" полностью интегрирована в MC Panel. Администраторы могут блокировать пользователей, которые нарушают правила или должны быть отстранены от использования панели.
|
|
||||||
|
|
||||||
### Тестирование
|
|
||||||
|
|
||||||
1. **Создайте тестового пользователя**
|
|
||||||
- Зарегистрируйте нового пользователя
|
|
||||||
|
|
||||||
2. **Заблокируйте его**
|
|
||||||
- Войдите как админ
|
|
||||||
- Откройте "Пользователи"
|
|
||||||
- Измените роль на "Забанен"
|
|
||||||
|
|
||||||
3. **Попробуйте войти**
|
|
||||||
- Выйдите из админа
|
|
||||||
- Войдите как заблокированный пользователь
|
|
||||||
- Вы увидите ошибку "Ваш аккаунт заблокирован"
|
|
||||||
|
|
||||||
4. **Разблокируйте**
|
|
||||||
- Войдите как админ
|
|
||||||
- Измените роль обратно на "Пользователь"
|
|
||||||
|
|
||||||
## 🎯 Использование
|
|
||||||
|
|
||||||
Роль "Забанен" готова к использованию. Используйте её для:
|
|
||||||
- Блокировки нарушителей
|
|
||||||
- Временного отстранения пользователей
|
|
||||||
- Защиты панели от нежелательных действий
|
|
||||||
|
|
||||||
**Будьте осторожны с блокировками! 🚨**
|
|
||||||
145
BUGFIX.md
145
BUGFIX.md
@@ -1,145 +0,0 @@
|
|||||||
# Исправление багов
|
|
||||||
|
|
||||||
## Исправленные проблемы
|
|
||||||
|
|
||||||
### 1. ✅ Обычные пользователи теперь могут создавать серверы
|
|
||||||
|
|
||||||
**Что изменилось:**
|
|
||||||
- Убрана проверка роли при создании сервера
|
|
||||||
- Любой авторизованный пользователь может создать сервер
|
|
||||||
- При создании сервера обычным пользователем, ему автоматически выдается доступ к этому серверу
|
|
||||||
- Кнопка "+" теперь видна всем пользователям
|
|
||||||
|
|
||||||
**Файлы:**
|
|
||||||
- `backend/main_new.py` - убрана проверка `if user["role"] != "admin"`
|
|
||||||
- `frontend/src/App_final.jsx` - кнопка создания доступна всем
|
|
||||||
|
|
||||||
### 2. ✅ Админ теперь может просматривать файлы, статистику, настройки и консоль
|
|
||||||
|
|
||||||
**Проблема:**
|
|
||||||
Компоненты не передавали токен авторизации в запросы к API.
|
|
||||||
|
|
||||||
**Что исправлено:**
|
|
||||||
- Все компоненты теперь принимают prop `token`
|
|
||||||
- Все запросы к API включают заголовок `Authorization: Bearer ${token}`
|
|
||||||
|
|
||||||
**Исправленные компоненты:**
|
|
||||||
- `Console.jsx` - добавлен токен в запросы команд
|
|
||||||
- `Stats.jsx` - добавлен токен в запросы статистики
|
|
||||||
- `FileManager.jsx` - добавлен токен во все файловые операции
|
|
||||||
- `ServerSettings.jsx` - добавлен токен в настройки
|
|
||||||
- `CreateServerModal.jsx` - добавлен токен при создании
|
|
||||||
|
|
||||||
## Что нужно сделать
|
|
||||||
|
|
||||||
### Если вы еще не переименовали файлы:
|
|
||||||
|
|
||||||
1. **Удалите старые файлы:**
|
|
||||||
```
|
|
||||||
backend/main.py (если есть)
|
|
||||||
frontend/src/App.jsx (если есть)
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Переименуйте новые файлы:**
|
|
||||||
```
|
|
||||||
backend/main_new.py → backend/main.py
|
|
||||||
frontend/src/App_final.jsx → frontend/src/App.jsx
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Перезапустите панель:**
|
|
||||||
```bash
|
|
||||||
START_PANEL.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
### Если файлы уже переименованы:
|
|
||||||
|
|
||||||
Просто перезапустите панель - изменения уже применены в `main_new.py` и `App_final.jsx`.
|
|
||||||
|
|
||||||
## Проверка исправлений
|
|
||||||
|
|
||||||
### Тест 1: Создание сервера обычным пользователем
|
|
||||||
|
|
||||||
1. Зарегистрируйте нового пользователя
|
|
||||||
2. Войдите под ним
|
|
||||||
3. Нажмите кнопку "+" в списке серверов
|
|
||||||
4. Создайте сервер
|
|
||||||
5. ✅ Сервер должен появиться в списке
|
|
||||||
|
|
||||||
### Тест 2: Просмотр файлов админом
|
|
||||||
|
|
||||||
1. Войдите как admin
|
|
||||||
2. Выберите любой сервер
|
|
||||||
3. Перейдите на вкладку "Файлы"
|
|
||||||
4. ✅ Должен отобразиться список файлов
|
|
||||||
|
|
||||||
### Тест 3: Просмотр статистики
|
|
||||||
|
|
||||||
1. Выберите сервер
|
|
||||||
2. Перейдите на вкладку "Статистика"
|
|
||||||
3. ✅ Должна отобразиться статистика (CPU, RAM, Disk)
|
|
||||||
|
|
||||||
### Тест 4: Консоль
|
|
||||||
|
|
||||||
1. Запустите сервер
|
|
||||||
2. Перейдите на вкладку "Консоль"
|
|
||||||
3. ✅ Должны появиться логи сервера
|
|
||||||
4. Отправьте команду (например, "list")
|
|
||||||
5. ✅ Команда должна выполниться
|
|
||||||
|
|
||||||
### Тест 5: Настройки
|
|
||||||
|
|
||||||
1. Перейдите на вкладку "Настройки"
|
|
||||||
2. ✅ Должны отобразиться настройки сервера
|
|
||||||
3. Измените что-нибудь и сохраните
|
|
||||||
4. ✅ Изменения должны сохраниться
|
|
||||||
|
|
||||||
## Дополнительные улучшения
|
|
||||||
|
|
||||||
### Автоматический доступ к созданным серверам
|
|
||||||
|
|
||||||
Теперь когда обычный пользователь создает сервер:
|
|
||||||
1. Сервер создается
|
|
||||||
2. Пользователю автоматически выдается доступ к этому серверу
|
|
||||||
3. Сервер сразу появляется в его списке
|
|
||||||
|
|
||||||
Админу не нужно вручную выдавать доступ!
|
|
||||||
|
|
||||||
### Логирование ошибок
|
|
||||||
|
|
||||||
Все ошибки API теперь выводятся в консоль браузера (F12) для отладки.
|
|
||||||
|
|
||||||
## Если что-то не работает
|
|
||||||
|
|
||||||
### Ошибка "Требуется авторизация"
|
|
||||||
|
|
||||||
**Причина:** Токен не передается в запросах
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Убедитесь что используете обновленные файлы
|
|
||||||
2. Очистите кэш браузера (Ctrl+Shift+Delete)
|
|
||||||
3. Выйдите и войдите заново
|
|
||||||
|
|
||||||
### Ошибка "Нет доступа к этому серверу"
|
|
||||||
|
|
||||||
**Причина:** У пользователя нет прав на сервер
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Если вы админ - проверьте что сервер существует
|
|
||||||
2. Если вы пользователь - попросите админа выдать доступ
|
|
||||||
3. Или создайте свой сервер - доступ выдастся автоматически
|
|
||||||
|
|
||||||
### Пустой список файлов
|
|
||||||
|
|
||||||
**Причина:** Токен не передается или сервер пустой
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Проверьте консоль браузера (F12) на ошибки
|
|
||||||
2. Убедитесь что используете обновленный FileManager.jsx
|
|
||||||
3. Загрузите файлы через кнопку "Загрузить"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Готово!** Все баги исправлены. Теперь:
|
|
||||||
- ✅ Любой пользователь может создавать серверы
|
|
||||||
- ✅ Админ может просматривать все вкладки
|
|
||||||
- ✅ Все запросы включают токен авторизации
|
|
||||||
191
CHANGELOG.md
191
CHANGELOG.md
@@ -1,191 +0,0 @@
|
|||||||
# 📝 История изменений MC Panel
|
|
||||||
|
|
||||||
## Версия 2.2 - Роль "Забанен" (14.01.2026)
|
|
||||||
|
|
||||||
### ✨ Новые возможности
|
|
||||||
|
|
||||||
#### ⛔ Роль "Забанен"
|
|
||||||
- Новая роль для блокировки пользователей
|
|
||||||
- Полная блокировка доступа к панели
|
|
||||||
- Проверка на уровне API (функция get_current_user)
|
|
||||||
- Красные индикаторы в интерфейсе
|
|
||||||
- Сообщение об ошибке при попытке входа
|
|
||||||
|
|
||||||
#### 🎨 Визуальное отображение
|
|
||||||
- Красная иконка в списке пользователей
|
|
||||||
- Красный бейдж "Забанен" в header
|
|
||||||
- Красный бейдж в личном кабинете
|
|
||||||
- Предупреждающие сообщения
|
|
||||||
|
|
||||||
#### 🔐 Безопасность
|
|
||||||
- Проверка роли перед каждым запросом к API
|
|
||||||
- Невозможно обойти блокировку
|
|
||||||
- Ошибка 403: "Ваш аккаунт заблокирован"
|
|
||||||
|
|
||||||
### 📁 Новые файлы
|
|
||||||
- `BANNED_ROLE.md` - документация роли "Забанен"
|
|
||||||
|
|
||||||
### 🔧 Изменения в коде
|
|
||||||
- `backend/main.py` - добавлена проверка на бан в get_current_user()
|
|
||||||
- `frontend/src/App.jsx` - добавлена функция getRoleName()
|
|
||||||
- `frontend/src/components/Users.jsx` - добавлена опция "Забанен"
|
|
||||||
- `frontend/src/components/Profile.jsx` - добавлено отображение роли "Забанен"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Версия 2.1 - Личный кабинет (14.01.2026)
|
|
||||||
|
|
||||||
### ✨ Новые возможности
|
|
||||||
|
|
||||||
#### 👤 Личный кабинет
|
|
||||||
- Кнопка "Личный кабинет" в header рядом с "Тикеты"
|
|
||||||
- Три вкладки: Обзор, Имя пользователя, Пароль
|
|
||||||
- Статистика профиля (серверы, тикеты, роль)
|
|
||||||
- Список своих серверов
|
|
||||||
- Изменение имени пользователя с подтверждением паролем
|
|
||||||
- Изменение пароля с проверкой
|
|
||||||
- Показ/скрытие паролей в формах
|
|
||||||
|
|
||||||
#### 🔐 Безопасность
|
|
||||||
- Проверка уникальности имени пользователя
|
|
||||||
- Автоматическое обновление владельцев серверов при смене имени
|
|
||||||
- Автоматическое обновление доступов к серверам
|
|
||||||
- Новый JWT токен при смене имени
|
|
||||||
- Хеширование паролей (bcrypt)
|
|
||||||
|
|
||||||
#### 📊 Статистика профиля
|
|
||||||
- Общее количество серверов
|
|
||||||
- Мои серверы (владелец)
|
|
||||||
- Доступные серверы
|
|
||||||
- Статистика по тикетам (всего, на рассмотрении, в работе, закрыто)
|
|
||||||
- Информация о роли
|
|
||||||
|
|
||||||
### 📁 Новые файлы
|
|
||||||
- `frontend/src/components/Profile.jsx` - компонент личного кабинета
|
|
||||||
- `PROFILE_SYSTEM.md` - документация личного кабинета
|
|
||||||
|
|
||||||
### 🔧 API Endpoints
|
|
||||||
- `PUT /api/profile/username` - изменить имя пользователя
|
|
||||||
- `PUT /api/profile/password` - изменить пароль
|
|
||||||
- `GET /api/profile/stats` - получить статистику профиля
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Версия 2.0 - Система тикетов (14.01.2026)
|
|
||||||
|
|
||||||
### ✨ Новые возможности
|
|
||||||
|
|
||||||
#### 🎫 Система тикетов
|
|
||||||
- Полноценная система поддержки с чатом
|
|
||||||
- Три статуса: На рассмотрении, В работе, Закрыт
|
|
||||||
- Автоматическое обновление сообщений каждые 3 секунды
|
|
||||||
- Системные сообщения о смене статуса
|
|
||||||
- Кнопка "Тикеты" в header
|
|
||||||
|
|
||||||
#### 👥 Новая роль "Тех. поддержка"
|
|
||||||
- Доступ ко всем тикетам
|
|
||||||
- Возможность менять статусы тикетов
|
|
||||||
- Возможность отвечать на тикеты
|
|
||||||
- Отдельный бейдж в интерфейсе
|
|
||||||
|
|
||||||
#### 🔧 Улучшения управления пользователями
|
|
||||||
- Выпадающий список для выбора роли
|
|
||||||
- Три роли: Пользователь, Тех. поддержка, Администратор
|
|
||||||
- Цветные индикаторы ролей
|
|
||||||
- Описание прав для каждой роли
|
|
||||||
|
|
||||||
### 🔐 Безопасность
|
|
||||||
- Изменён логин администратора: `Sofa12345`
|
|
||||||
- Изменён пароль администратора: `arkonsad123`
|
|
||||||
|
|
||||||
### 📁 Новые файлы
|
|
||||||
- `backend/tickets.json` - хранилище тикетов
|
|
||||||
- `frontend/src/components/Tickets.jsx` - список тикетов
|
|
||||||
- `frontend/src/components/TicketChat.jsx` - чат тикета
|
|
||||||
- `frontend/src/components/CreateTicketModal.jsx` - создание тикета
|
|
||||||
- `TICKETS_SYSTEM.md` - документация системы тикетов
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Версия 1.5 - Система тем (14.01.2026)
|
|
||||||
|
|
||||||
### 🎨 Темы
|
|
||||||
- 5 тем: Тёмная, Светлая, Фиолетовая, Синяя, Зелёная
|
|
||||||
- Градиентный логотип "MC Panel" для каждой темы
|
|
||||||
- Селектор тем в header
|
|
||||||
- Автоматическое сохранение выбранной темы
|
|
||||||
|
|
||||||
### 🎯 Дизайн
|
|
||||||
- Современный интерфейс в стиле TimeWeb Cloud
|
|
||||||
- Карточки с тенями и анимациями
|
|
||||||
- Плавные переходы между темами
|
|
||||||
- Адаптивный дизайн для мобильных
|
|
||||||
|
|
||||||
### 📁 Файлы
|
|
||||||
- `frontend/src/themes.js` - конфигурация тем
|
|
||||||
- `frontend/src/components/ThemeSelector.jsx` - селектор тем
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Версия 1.0 - Базовая панель (13.01.2026)
|
|
||||||
|
|
||||||
### 🖥️ Управление серверами
|
|
||||||
- Создание и удаление серверов
|
|
||||||
- Запуск и остановка серверов
|
|
||||||
- Просмотр консоли в реальном времени
|
|
||||||
- Менеджер файлов с редактированием
|
|
||||||
- Мониторинг ресурсов (RAM, диск)
|
|
||||||
- Настройки сервера
|
|
||||||
|
|
||||||
### 👥 Система пользователей
|
|
||||||
- Регистрация и авторизация
|
|
||||||
- JWT токены
|
|
||||||
- Роли: Админ и Пользователь
|
|
||||||
- Управление доступом к серверам
|
|
||||||
- Владельцы серверов
|
|
||||||
|
|
||||||
### 🌐 Сетевой доступ
|
|
||||||
- Работа через Radmin VPN
|
|
||||||
- Автоматическое определение API URL
|
|
||||||
- Поддержка локальной и сетевой работы
|
|
||||||
|
|
||||||
### 📁 Основные файлы
|
|
||||||
- `backend/main.py` - FastAPI бэкенд
|
|
||||||
- `frontend/src/App.jsx` - React фронтенд
|
|
||||||
- `backend/users.json` - хранилище пользователей
|
|
||||||
- `backend/servers/` - папка с серверами
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Планы на будущее
|
|
||||||
|
|
||||||
### Версия 2.1
|
|
||||||
- [ ] Уведомления о новых сообщениях в тикетах
|
|
||||||
- [ ] Прикрепление файлов к тикетам
|
|
||||||
- [ ] Фильтрация тикетов по статусу
|
|
||||||
- [ ] Поиск по тикетам
|
|
||||||
|
|
||||||
### Версия 2.2
|
|
||||||
- [ ] Статистика по тикетам
|
|
||||||
- [ ] Экспорт истории тикетов
|
|
||||||
- [ ] Шаблоны ответов для тех. поддержки
|
|
||||||
- [ ] Приоритеты тикетов
|
|
||||||
|
|
||||||
### Версия 3.0
|
|
||||||
- [ ] Плагины для серверов
|
|
||||||
- [ ] Автоматическое резервное копирование
|
|
||||||
- [ ] Планировщик задач
|
|
||||||
- [ ] Мониторинг производительности
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 Поддержка
|
|
||||||
|
|
||||||
Если у вас возникли вопросы или проблемы:
|
|
||||||
1. Создайте тикет в системе поддержки
|
|
||||||
2. Опишите проблему подробно
|
|
||||||
3. Дождитесь ответа от тех. поддержки
|
|
||||||
|
|
||||||
**Учётные данные администратора:**
|
|
||||||
- Логин: `Sofa12345`
|
|
||||||
- Пароль: `arkonsad123`
|
|
||||||
155
DEBUG_GUIDE.md
155
DEBUG_GUIDE.md
@@ -1,155 +0,0 @@
|
|||||||
# Руководство по отладке проблем
|
|
||||||
|
|
||||||
## Проблема: После запуска сервера пропадают файлы/настройки/статистика
|
|
||||||
|
|
||||||
### Причина
|
|
||||||
Процесс сервера блокирует выполнение или завершается с ошибкой.
|
|
||||||
|
|
||||||
### Диагностика
|
|
||||||
|
|
||||||
1. **Проверьте логи бэкенда** (терминал где запущен `python main.py`):
|
|
||||||
```
|
|
||||||
Сервер test_server запущен с PID 12345
|
|
||||||
Начало чтения вывода для сервера test_server
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Проверьте консоль браузера** (F12):
|
|
||||||
```javascript
|
|
||||||
// Должны быть логи:
|
|
||||||
Сервер запущен: {message: "Сервер запущен", pid: 12345}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Проверьте, запустился ли Java процесс**:
|
|
||||||
```bash
|
|
||||||
# Windows
|
|
||||||
tasklist | findstr java
|
|
||||||
|
|
||||||
# Должен показать процесс java.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Проверьте наличие server.jar**:
|
|
||||||
- Откройте папку `backend/servers/ИМЯ_СЕРВЕРА/`
|
|
||||||
- Убедитесь, что там есть файл `server.jar` или другой .jar файл
|
|
||||||
- Проверьте команду запуска в настройках сервера
|
|
||||||
|
|
||||||
### Решение
|
|
||||||
|
|
||||||
#### Если server.jar отсутствует:
|
|
||||||
|
|
||||||
1. Скачайте server.jar для Minecraft
|
|
||||||
2. Загрузите через менеджер файлов в панели
|
|
||||||
3. Убедитесь, что команда запуска правильная
|
|
||||||
|
|
||||||
#### Если Java не установлена:
|
|
||||||
|
|
||||||
1. Установите Java 17 или новее
|
|
||||||
2. Проверьте установку:
|
|
||||||
```bash
|
|
||||||
java -version
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Если процесс запускается но сразу завершается:
|
|
||||||
|
|
||||||
1. Проверьте логи в консоли панели
|
|
||||||
2. Возможно нужно принять EULA:
|
|
||||||
- Откройте файл `eula.txt` через редактор в панели
|
|
||||||
- Измените `eula=false` на `eula=true`
|
|
||||||
- Сохраните и перезапустите сервер
|
|
||||||
|
|
||||||
#### Если команда запуска неправильная:
|
|
||||||
|
|
||||||
1. Перейдите в Настройки сервера
|
|
||||||
2. Измените команду запуска, например:
|
|
||||||
```
|
|
||||||
java -Xmx2G -Xms1G -jar server.jar nogui
|
|
||||||
```
|
|
||||||
3. Сохраните настройки
|
|
||||||
4. Запустите сервер
|
|
||||||
|
|
||||||
## Проблема: Сервер не останавливается
|
|
||||||
|
|
||||||
### Причина
|
|
||||||
Процесс не отвечает на команду stop.
|
|
||||||
|
|
||||||
### Решение
|
|
||||||
|
|
||||||
1. **Через панель**: Подождите 30 секунд, процесс будет принудительно завершен
|
|
||||||
|
|
||||||
2. **Вручную через Task Manager**:
|
|
||||||
- Откройте Диспетчер задач (Ctrl+Shift+Esc)
|
|
||||||
- Найдите процесс `java.exe`
|
|
||||||
- Завершите процесс
|
|
||||||
- Обновите страницу панели
|
|
||||||
|
|
||||||
## Проблема: Консоль не показывает логи
|
|
||||||
|
|
||||||
### Причина
|
|
||||||
WebSocket не подключается или процесс не выводит логи.
|
|
||||||
|
|
||||||
### Диагностика
|
|
||||||
|
|
||||||
1. **Проверьте консоль браузера**:
|
|
||||||
```
|
|
||||||
WebSocket подключен
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Проверьте логи бэкенда**:
|
|
||||||
```
|
|
||||||
WebSocket подключен для сервера: test_server
|
|
||||||
Отправка X существующих логов
|
|
||||||
```
|
|
||||||
|
|
||||||
### Решение
|
|
||||||
|
|
||||||
1. Перезапустите сервер
|
|
||||||
2. Обновите страницу панели (F5)
|
|
||||||
3. Проверьте, что сервер действительно запущен
|
|
||||||
|
|
||||||
## Проблема: Статистика показывает неправильный статус
|
|
||||||
|
|
||||||
### Причина
|
|
||||||
Процесс завершился, но панель не обновилась.
|
|
||||||
|
|
||||||
### Решение
|
|
||||||
|
|
||||||
1. Обновите страницу (F5)
|
|
||||||
2. Статус обновляется автоматически каждые 5 секунд
|
|
||||||
3. Проверьте логи бэкенда на наличие ошибок
|
|
||||||
|
|
||||||
## Полезные команды для отладки
|
|
||||||
|
|
||||||
### Проверка портов
|
|
||||||
```bash
|
|
||||||
# Windows
|
|
||||||
netstat -ano | findstr :8000
|
|
||||||
netstat -ano | findstr :3000
|
|
||||||
```
|
|
||||||
|
|
||||||
### Проверка процессов Java
|
|
||||||
```bash
|
|
||||||
# Windows
|
|
||||||
tasklist | findstr java
|
|
||||||
|
|
||||||
# Убить все процессы Java (ОСТОРОЖНО!)
|
|
||||||
taskkill /F /IM java.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
### Очистка и перезапуск
|
|
||||||
|
|
||||||
1. Остановите все серверы в панели
|
|
||||||
2. Закройте бэкенд (Ctrl+C)
|
|
||||||
3. Закройте фронтенд (Ctrl+C)
|
|
||||||
4. Убейте все процессы Java если нужно
|
|
||||||
5. Запустите бэкенд заново
|
|
||||||
6. Запустите фронтенд заново
|
|
||||||
7. Обновите страницу в браузере
|
|
||||||
|
|
||||||
## Логи для отправки при обращении за помощью
|
|
||||||
|
|
||||||
Если проблема не решается, соберите следующую информацию:
|
|
||||||
|
|
||||||
1. **Логи бэкенда** (последние 50 строк из терминала)
|
|
||||||
2. **Консоль браузера** (F12 → Console, скриншот или текст)
|
|
||||||
3. **Network вкладка** (F12 → Network, покажите неудачные запросы)
|
|
||||||
4. **Содержимое папки сервера** (список файлов)
|
|
||||||
5. **Команда запуска** из настроек сервера
|
|
||||||
163
FINAL_STEPS.md
163
FINAL_STEPS.md
@@ -1,163 +0,0 @@
|
|||||||
# Финальные шаги для запуска панели с авторизацией
|
|
||||||
|
|
||||||
## Шаг 1: Переименуйте файлы
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
1. Откройте папку `backend`
|
|
||||||
2. Удалите файл `main.py` (если есть)
|
|
||||||
3. Переименуйте `main_new.py` в `main.py`
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
1. Откройте папку `frontend/src`
|
|
||||||
2. Удалите файл `App.jsx` (если есть)
|
|
||||||
3. Переименуйте `App_final.jsx` в `App.jsx`
|
|
||||||
|
|
||||||
## Шаг 2: Установите зависимости
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Новые зависимости:
|
|
||||||
- passlib[bcrypt] - для хеширования паролей
|
|
||||||
- python-jose[cryptography] - для JWT токенов
|
|
||||||
|
|
||||||
## Шаг 3: Запустите панель
|
|
||||||
|
|
||||||
### Вариант 1: Автоматический запуск
|
|
||||||
```bash
|
|
||||||
START_PANEL.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
### Вариант 2: Ручной запуск
|
|
||||||
|
|
||||||
**Терминал 1 - Бэкенд:**
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**Терминал 2 - Фронтенд:**
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Шаг 4: Первый вход
|
|
||||||
|
|
||||||
1. Откройте http://localhost:3000
|
|
||||||
2. Войдите с учетными данными:
|
|
||||||
- **Логин:** admin
|
|
||||||
- **Пароль:** admin
|
|
||||||
3. Вы попадете в панель управления
|
|
||||||
|
|
||||||
## Что нового
|
|
||||||
|
|
||||||
### ✅ Система авторизации
|
|
||||||
- Вход и регистрация пользователей
|
|
||||||
- JWT токены для безопасности
|
|
||||||
- Автоматический выход при истечении токена
|
|
||||||
|
|
||||||
### ✅ Роли пользователей
|
|
||||||
- **Администратор** - полный доступ ко всем функциям
|
|
||||||
- **Пользователь** - доступ только к назначенным серверам
|
|
||||||
|
|
||||||
### ✅ Управление пользователями
|
|
||||||
- Кнопка "Пользователи" в шапке (только для админов)
|
|
||||||
- Выдача/отзыв доступа к серверам
|
|
||||||
- Изменение ролей пользователей
|
|
||||||
- Удаление пользователей
|
|
||||||
|
|
||||||
### ✅ Контроль доступа
|
|
||||||
- Пользователи видят только свои серверы
|
|
||||||
- Админы видят все серверы
|
|
||||||
- Проверка прав на каждое действие
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
### Создание нового пользователя
|
|
||||||
|
|
||||||
**Вариант 1: Регистрация**
|
|
||||||
1. На странице входа нажмите "Регистрация"
|
|
||||||
2. Введите логин и пароль
|
|
||||||
3. Новый пользователь создастся с ролью "Пользователь"
|
|
||||||
|
|
||||||
**Вариант 2: Админ создает**
|
|
||||||
1. Попросите пользователя зарегистрироваться
|
|
||||||
2. Админ выдает ему доступ к нужным серверам
|
|
||||||
|
|
||||||
### Выдача доступа к серверу
|
|
||||||
|
|
||||||
1. Войдите как администратор
|
|
||||||
2. Нажмите кнопку "Пользователи"
|
|
||||||
3. Найдите нужного пользователя
|
|
||||||
4. Нажмите на название сервера (станет зеленым)
|
|
||||||
5. Пользователь сразу увидит этот сервер
|
|
||||||
|
|
||||||
### Изменение роли
|
|
||||||
|
|
||||||
1. В разделе "Пользователи"
|
|
||||||
2. Нажмите "Сделать админом" или "Сделать пользователем"
|
|
||||||
3. Подтвердите действие
|
|
||||||
|
|
||||||
## Безопасность
|
|
||||||
|
|
||||||
### Смените секретный ключ!
|
|
||||||
|
|
||||||
Откройте `backend/main.py` и измените:
|
|
||||||
```python
|
|
||||||
SECRET_KEY = "your-secret-key-change-this-in-production-12345"
|
|
||||||
```
|
|
||||||
|
|
||||||
На случайную строку длиной минимум 32 символа.
|
|
||||||
|
|
||||||
### Смените пароль администратора
|
|
||||||
|
|
||||||
1. Войдите как admin
|
|
||||||
2. Создайте нового администратора с другим паролем
|
|
||||||
3. Войдите под новым админом
|
|
||||||
4. Удалите старого admin
|
|
||||||
|
|
||||||
## Файлы данных
|
|
||||||
|
|
||||||
- `backend/users.json` - база пользователей (НЕ УДАЛЯЙТЕ!)
|
|
||||||
- `backend/servers/` - папки серверов
|
|
||||||
- `backend/servers/*/panel_config.json` - настройки каждого сервера
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### "Требуется авторизация"
|
|
||||||
Токен истек. Выйдите и войдите заново.
|
|
||||||
|
|
||||||
### Не могу войти
|
|
||||||
Удалите `backend/users.json` и перезапустите бэкенд. Создастся новый admin/admin.
|
|
||||||
|
|
||||||
### Пользователь не видит серверы
|
|
||||||
Проверьте что админ выдал ему доступ в разделе "Пользователи".
|
|
||||||
|
|
||||||
### Ошибка импорта passlib или jose
|
|
||||||
Установите зависимости:
|
|
||||||
```bash
|
|
||||||
pip install passlib[bcrypt] python-jose[cryptography]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Доступ через сеть
|
|
||||||
|
|
||||||
Всё работает так же как раньше:
|
|
||||||
|
|
||||||
1. Узнайте ваш IP в Radmin VPN: `ipconfig`
|
|
||||||
2. Друг открывает: `http://ВАШ_IP:3000`
|
|
||||||
3. Друг регистрируется
|
|
||||||
4. Вы выдаете ему доступ к нужным серверам
|
|
||||||
|
|
||||||
## Готово!
|
|
||||||
|
|
||||||
Теперь у вас полноценная панель управления с:
|
|
||||||
- ✅ Авторизацией и регистрацией
|
|
||||||
- ✅ Ролями и правами доступа
|
|
||||||
- ✅ Управлением пользователями
|
|
||||||
- ✅ Контролем доступа к серверам
|
|
||||||
- ✅ Всеми предыдущими функциями
|
|
||||||
|
|
||||||
Подробнее см. `AUTH_SETUP.md`
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
# ✅ Установка завершена!
|
|
||||||
|
|
||||||
## Что было создано
|
|
||||||
|
|
||||||
### Backend (FastAPI)
|
|
||||||
- ✅ Система авторизации с JWT токенами
|
|
||||||
- ✅ Управление пользователями и ролями
|
|
||||||
- ✅ Контроль доступа к серверам
|
|
||||||
- ✅ API для всех операций с серверами
|
|
||||||
- ✅ WebSocket для консоли в реальном времени
|
|
||||||
- ✅ Файловый менеджер с редактором
|
|
||||||
- ✅ Мониторинг ресурсов
|
|
||||||
|
|
||||||
### Frontend (React)
|
|
||||||
- ✅ Форма входа и регистрации
|
|
||||||
- ✅ Управление пользователями (для админов)
|
|
||||||
- ✅ Панель управления серверами
|
|
||||||
- ✅ Консоль с логами
|
|
||||||
- ✅ Файловый менеджер
|
|
||||||
- ✅ Статистика ресурсов
|
|
||||||
- ✅ Настройки серверов
|
|
||||||
|
|
||||||
## Финальные шаги
|
|
||||||
|
|
||||||
### 1. Переименуйте файлы
|
|
||||||
|
|
||||||
**Backend:**
|
|
||||||
```
|
|
||||||
backend/main_new.py → backend/main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**Frontend:**
|
|
||||||
```
|
|
||||||
frontend/src/App_final.jsx → frontend/src/App.jsx
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Установите зависимости
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Новые зависимости:
|
|
||||||
- `passlib[bcrypt]` - хеширование паролей
|
|
||||||
- `python-jose[cryptography]` - JWT токены
|
|
||||||
|
|
||||||
### 3. Запустите панель
|
|
||||||
|
|
||||||
**Автоматически:**
|
|
||||||
```bash
|
|
||||||
START_PANEL.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
**Вручную:**
|
|
||||||
```bash
|
|
||||||
# Терминал 1
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
|
|
||||||
# Терминал 2
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Первый вход
|
|
||||||
|
|
||||||
1. Откройте http://localhost:3000
|
|
||||||
2. Войдите:
|
|
||||||
- Логин: `admin`
|
|
||||||
- Пароль: `admin`
|
|
||||||
|
|
||||||
## Основные функции
|
|
||||||
|
|
||||||
### Для администраторов
|
|
||||||
|
|
||||||
1. **Создание серверов** - кнопка "+" в боковой панели
|
|
||||||
2. **Управление пользователями** - кнопка "Пользователи" в шапке
|
|
||||||
3. **Выдача доступа** - нажимайте на названия серверов в карточке пользователя
|
|
||||||
4. **Изменение ролей** - кнопка "Сделать админом/пользователем"
|
|
||||||
5. **Настройки серверов** - вкладка "Настройки"
|
|
||||||
|
|
||||||
### Для пользователей
|
|
||||||
|
|
||||||
1. **Просмотр своих серверов** - только те, к которым есть доступ
|
|
||||||
2. **Запуск/остановка** - кнопки на карточке сервера
|
|
||||||
3. **Консоль** - просмотр логов и отправка команд
|
|
||||||
4. **Файлы** - управление файлами сервера
|
|
||||||
5. **Статистика** - мониторинг ресурсов
|
|
||||||
|
|
||||||
## Безопасность
|
|
||||||
|
|
||||||
### ⚠️ ВАЖНО: Смените секретный ключ!
|
|
||||||
|
|
||||||
Откройте `backend/main.py` и измените:
|
|
||||||
```python
|
|
||||||
SECRET_KEY = "your-secret-key-change-this-in-production-12345"
|
|
||||||
```
|
|
||||||
|
|
||||||
На случайную строку минимум 32 символа.
|
|
||||||
|
|
||||||
### Смените пароль администратора
|
|
||||||
|
|
||||||
1. Создайте нового администратора
|
|
||||||
2. Войдите под ним
|
|
||||||
3. Удалите старого admin
|
|
||||||
|
|
||||||
## Доступ через сеть (Radmin VPN)
|
|
||||||
|
|
||||||
### На вашем компьютере:
|
|
||||||
1. Узнайте IP: `ipconfig` (ищите Radmin VPN, обычно 26.x.x.x)
|
|
||||||
2. Запустите панель
|
|
||||||
3. Откройте: http://localhost:3000
|
|
||||||
|
|
||||||
### На компьютере друга:
|
|
||||||
1. Откройте: http://ВАШ_IP:3000
|
|
||||||
2. Зарегистрируйтесь
|
|
||||||
3. Попросите вас выдать доступ к серверам
|
|
||||||
|
|
||||||
### Откройте порты (если не работает):
|
|
||||||
```powershell
|
|
||||||
netsh advfirewall firewall add rule name="MC Panel Backend" dir=in action=allow protocol=TCP localport=8000
|
|
||||||
netsh advfirewall firewall add rule name="MC Panel Frontend" dir=in action=allow protocol=TCP localport=3000
|
|
||||||
```
|
|
||||||
|
|
||||||
## Структура файлов
|
|
||||||
|
|
||||||
```
|
|
||||||
mc-panel/
|
|
||||||
├── START_PANEL.bat # Автозапуск
|
|
||||||
├── FINAL_STEPS.md # Инструкция
|
|
||||||
├── AUTH_SETUP.md # Руководство по авторизации
|
|
||||||
├── backend/
|
|
||||||
│ ├── main_new.py # Новый бэкенд (переименуйте в main.py)
|
|
||||||
│ ├── requirements.txt # Зависимости
|
|
||||||
│ ├── users.json # База пользователей (создастся автоматически)
|
|
||||||
│ └── servers/ # Папки серверов
|
|
||||||
└── frontend/
|
|
||||||
├── src/
|
|
||||||
│ ├── App_final.jsx # Новый App (переименуйте в App.jsx)
|
|
||||||
│ └── components/
|
|
||||||
│ ├── Auth.jsx # Форма входа
|
|
||||||
│ ├── Users.jsx # Управление пользователями
|
|
||||||
│ ├── Console.jsx # Консоль (обновлен)
|
|
||||||
│ ├── FileManager.jsx # Файлы (обновлен)
|
|
||||||
│ ├── Stats.jsx # Статистика (обновлен)
|
|
||||||
│ └── ...
|
|
||||||
└── package.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Ошибка импорта passlib или jose
|
|
||||||
```bash
|
|
||||||
pip install passlib[bcrypt] python-jose[cryptography]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Не могу войти
|
|
||||||
Удалите `backend/users.json` и перезапустите бэкенд.
|
|
||||||
|
|
||||||
### Пользователь не видит серверы
|
|
||||||
Админ должен выдать доступ в разделе "Пользователи".
|
|
||||||
|
|
||||||
### Токен истек
|
|
||||||
Выйдите и войдите заново.
|
|
||||||
|
|
||||||
## Документация
|
|
||||||
|
|
||||||
- `FINAL_STEPS.md` - пошаговая инструкция
|
|
||||||
- `AUTH_SETUP.md` - полное руководство по авторизации
|
|
||||||
- `QUICK_START.md` - быстрый старт
|
|
||||||
- `DEBUG_GUIDE.md` - отладка проблем
|
|
||||||
- `NETWORK_SETUP.md` - настройка сети
|
|
||||||
|
|
||||||
## Готово! 🎉
|
|
||||||
|
|
||||||
Теперь у вас есть полноценная панель управления Minecraft серверами с:
|
|
||||||
- ✅ Авторизацией и регистрацией
|
|
||||||
- ✅ Ролями и правами доступа
|
|
||||||
- ✅ Управлением пользователями
|
|
||||||
- ✅ Контролем доступа к серверам
|
|
||||||
- ✅ Консолью в реальном времени
|
|
||||||
- ✅ Файловым менеджером с редактором
|
|
||||||
- ✅ Мониторингом ресурсов
|
|
||||||
- ✅ Поддержкой удаленного доступа
|
|
||||||
|
|
||||||
**Приятного использования!** 🚀
|
|
||||||
707
MC_Panel_API.postman_collection.json
Normal file
707
MC_Panel_API.postman_collection.json
Normal file
@@ -0,0 +1,707 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"name": "MC Panel API",
|
||||||
|
"description": "API коллекция для MC Panel - системы управления Minecraft серверами",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "{{token}}",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"variable": [
|
||||||
|
{
|
||||||
|
"key": "baseUrl",
|
||||||
|
"value": "http://localhost:8000",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "serverName",
|
||||||
|
"value": "survival",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Authentication",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Register",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"if (pm.response.code === 200) {",
|
||||||
|
" const response = pm.response.json();",
|
||||||
|
" pm.collectionVariables.set('token', response.access_token);",
|
||||||
|
" pm.environment.set('token', response.access_token);",
|
||||||
|
"}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"username\": \"testuser\",\n \"password\": \"testpass123\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/auth/register",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "auth", "register"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Login",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"exec": [
|
||||||
|
"if (pm.response.code === 200) {",
|
||||||
|
" const response = pm.response.json();",
|
||||||
|
" pm.collectionVariables.set('token', response.access_token);",
|
||||||
|
" pm.environment.set('token', response.access_token);",
|
||||||
|
"}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"username\": \"Sofa12345\",\n \"password\": \"arkonsad123\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/auth/login",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "auth", "login"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get Current User",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/auth/me",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "auth", "me"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Users",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Get All Users",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/users",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "users"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Update User Role",
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"role\": \"support\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/users/username/role",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "users", "username", "role"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Update User Servers",
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"servers\": [\"survival\", \"creative\"]\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/users/username/servers",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "users", "username", "servers"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Delete User",
|
||||||
|
"request": {
|
||||||
|
"method": "DELETE",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/users/username",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "users", "username"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Servers",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Get Servers",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create Server",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"name\": \"survival\",\n \"displayName\": \"Survival Server\",\n \"startCommand\": \"java -Xmx2G -Xms1G -jar server.jar nogui\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/create",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "create"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get Server Config",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/config",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "config"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Update Server Config",
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"name\": \"survival\",\n \"displayName\": \"Updated Survival Server\",\n \"startCommand\": \"java -Xmx4G -Xms2G -jar server.jar nogui\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/config",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "config"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Start Server",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/start",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "start"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Stop Server",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/stop",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "stop"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Send Command",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"command\": \"say Hello from API!\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/command",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "command"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get Server Stats",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/stats",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "stats"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Delete Server",
|
||||||
|
"request": {
|
||||||
|
"method": "DELETE",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Files",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "List Files",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/files?path=",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "files"],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "path",
|
||||||
|
"value": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create File",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"type\": \"file\",\n \"name\": \"test.txt\",\n \"path\": \"\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/files/create",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "files", "create"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create Folder",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"type\": \"folder\",\n \"name\": \"backup\",\n \"path\": \"\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/files/create",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "files", "create"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get File Content",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/files/content?path=server.properties",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "files", "content"],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "path",
|
||||||
|
"value": "server.properties"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Update File Content",
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"content\": \"server-port=25565\\nmax-players=20\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/files/content?path=server.properties",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "files", "content"],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "path",
|
||||||
|
"value": "server.properties"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rename File",
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/files/rename?old_path=test.txt&new_name=test_renamed.txt",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "files", "rename"],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "old_path",
|
||||||
|
"value": "test.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "new_name",
|
||||||
|
"value": "test_renamed.txt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Move File",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"source\": \"test.txt\",\n \"destination\": \"backup\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/files/move",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "files", "move"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Delete File",
|
||||||
|
"request": {
|
||||||
|
"method": "DELETE",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/files?path=test.txt",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "files"],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "path",
|
||||||
|
"value": "test.txt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Download File",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/servers/{{serverName}}/files/download?path=server.jar",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "servers", "{{serverName}}", "files", "download"],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "path",
|
||||||
|
"value": "server.jar"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tickets",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Get Tickets",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/tickets",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "tickets"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create Ticket",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"title\": \"Проблема с сервером\",\n \"description\": \"Сервер не запускается после обновления\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/tickets/create",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "tickets", "create"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get Ticket",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/tickets/1",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "tickets", "1"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Add Message",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"text\": \"Я попробовал перезапустить, но проблема осталась\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/tickets/1/message",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "tickets", "1", "message"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Update Status",
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"status\": \"in_progress\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/tickets/1/status",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "tickets", "1", "status"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Profile",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Get Profile Stats",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/profile/stats",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "profile", "stats"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get User Profile Stats",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/profile/stats/username",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "profile", "stats", "username"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Update Username",
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"new_username\": \"newusername\",\n \"password\": \"currentpassword\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/profile/username",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "profile", "username"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Update Password",
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"old_password\": \"oldpass123\",\n \"new_password\": \"newpass456\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/profile/password",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "profile", "password"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OpenID Connect",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Get OIDC Providers",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "noauth"
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/auth/oidc/providers",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "auth", "oidc", "providers"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
114
NETWORK_SETUP.md
114
NETWORK_SETUP.md
@@ -1,114 +0,0 @@
|
|||||||
# Настройка доступа через сеть
|
|
||||||
|
|
||||||
## Быстрый старт для Radmin VPN
|
|
||||||
|
|
||||||
### 1. Узнайте ваш IP адрес в Radmin VPN
|
|
||||||
|
|
||||||
Откройте командную строку и выполните:
|
|
||||||
```bash
|
|
||||||
ipconfig
|
|
||||||
```
|
|
||||||
|
|
||||||
Найдите адаптер Radmin VPN, IP обычно выглядит как `26.x.x.x`
|
|
||||||
|
|
||||||
### 2. Запустите бэкенд
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Бэкенд автоматически слушает на всех сетевых интерфейсах (0.0.0.0:8000)
|
|
||||||
|
|
||||||
### 3. Запустите фронтенд
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Теперь фронтенд запускается с флагом `--host` по умолчанию.
|
|
||||||
|
|
||||||
### 4. Откройте в браузере
|
|
||||||
|
|
||||||
**На вашем компьютере:**
|
|
||||||
- http://localhost:3000
|
|
||||||
|
|
||||||
**На компьютере друга:**
|
|
||||||
- http://ВАШ_RADMIN_IP:3000
|
|
||||||
- Например: http://26.123.45.67:3000
|
|
||||||
|
|
||||||
## Автоматическое определение API
|
|
||||||
|
|
||||||
Фронтенд автоматически определяет правильный API URL:
|
|
||||||
- Если открыто через `localhost` → подключится к `http://localhost:8000`
|
|
||||||
- Если открыто через IP → подключится к `http://ВАШ_IP:8000`
|
|
||||||
|
|
||||||
## Ручная настройка (если автоматика не работает)
|
|
||||||
|
|
||||||
Создайте файл `frontend/.env.local`:
|
|
||||||
|
|
||||||
```env
|
|
||||||
VITE_API_URL=http://26.123.45.67:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
Замените `26.123.45.67` на ваш реальный IP в Radmin VPN.
|
|
||||||
|
|
||||||
Перезапустите фронтенд:
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Проверка подключения
|
|
||||||
|
|
||||||
1. Откройте консоль браузера (F12)
|
|
||||||
2. Проверьте, нет ли ошибок подключения
|
|
||||||
3. Убедитесь, что запросы идут на правильный IP адрес
|
|
||||||
|
|
||||||
## Возможные проблемы
|
|
||||||
|
|
||||||
### Серверы не загружаются
|
|
||||||
|
|
||||||
**Причина:** Фронтенд не может подключиться к бэкенду
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Убедитесь, что бэкенд запущен
|
|
||||||
2. Проверьте, что используется правильный IP
|
|
||||||
3. Проверьте брандмауэр Windows (порты 8000 и 3000 должны быть открыты)
|
|
||||||
|
|
||||||
### Ошибка при создании сервера
|
|
||||||
|
|
||||||
**Причина:** CORS или неправильный API URL
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Перезапустите бэкенд
|
|
||||||
2. Очистите кэш браузера (Ctrl+Shift+Delete)
|
|
||||||
3. Проверьте консоль браузера на ошибки
|
|
||||||
|
|
||||||
### WebSocket не подключается (консоль не работает)
|
|
||||||
|
|
||||||
**Причина:** WebSocket использует неправильный адрес
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Проверьте файл `frontend/src/config.js`
|
|
||||||
2. WebSocket должен использовать `ws://` вместо `http://`
|
|
||||||
3. Перезапустите фронтенд
|
|
||||||
|
|
||||||
## Открытие портов в брандмауэре Windows
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# Откройте PowerShell от имени администратора
|
|
||||||
|
|
||||||
# Порт для бэкенда
|
|
||||||
netsh advfirewall firewall add rule name="MC Panel Backend" dir=in action=allow protocol=TCP localport=8000
|
|
||||||
|
|
||||||
# Порт для фронтенда
|
|
||||||
netsh advfirewall firewall add rule name="MC Panel Frontend" dir=in action=allow protocol=TCP localport=3000
|
|
||||||
```
|
|
||||||
|
|
||||||
## Проверка работы
|
|
||||||
|
|
||||||
На компьютере друга откройте:
|
|
||||||
- http://ВАШ_IP:3000
|
|
||||||
|
|
||||||
Вы должны увидеть панель управления, и она должна показывать ваши серверы.
|
|
||||||
150
OIDC_CHANGES.md
150
OIDC_CHANGES.md
@@ -1,150 +0,0 @@
|
|||||||
# ✅ Изменения OpenID Connect - ZITADEL
|
|
||||||
|
|
||||||
## Что было сделано
|
|
||||||
|
|
||||||
### 1. Backend (Python/FastAPI)
|
|
||||||
|
|
||||||
#### `backend/main.py`
|
|
||||||
- ✅ Упрощена инициализация OAuth для работы только с ZITADEL
|
|
||||||
- ✅ Улучшена обработка callback с более детальным логированием
|
|
||||||
- ✅ Добавлена проверка наличия userinfo в токене
|
|
||||||
- ✅ Улучшена обработка ошибок OAuth
|
|
||||||
- ✅ **Исправлен импорт:** `starlette_client` вместо `fastapi_client` (FastAPI основан на Starlette)
|
|
||||||
|
|
||||||
#### `backend/oidc_config.py`
|
|
||||||
- ✅ Оставлен только ZITADEL провайдер
|
|
||||||
- ✅ Настройка через переменные окружения:
|
|
||||||
- `ZITADEL_ISSUER` - URL инстанса ZITADEL
|
|
||||||
- `ZITADEL_CLIENT_ID` - ID приложения
|
|
||||||
- `ZITADEL_CLIENT_SECRET` - Секретный ключ
|
|
||||||
|
|
||||||
#### `backend/.env.example`
|
|
||||||
- ✅ Обновлён с настройками ZITADEL
|
|
||||||
- ✅ Удалены старые провайдеры (Google, Microsoft, Discord, GitHub)
|
|
||||||
|
|
||||||
### 2. Frontend (React)
|
|
||||||
|
|
||||||
#### `frontend/src/components/Auth.jsx`
|
|
||||||
- ✅ Динамическая загрузка OIDC провайдеров
|
|
||||||
- ✅ Кнопка "Войти через ZITADEL" с иконкой 🔐
|
|
||||||
- ✅ Автоматическое скрытие если ZITADEL не настроен
|
|
||||||
|
|
||||||
#### `frontend/src/App.jsx`
|
|
||||||
- ✅ Обработка callback от ZITADEL в useEffect
|
|
||||||
- ✅ Автоматическое сохранение токена
|
|
||||||
- ✅ Очистка URL после входа
|
|
||||||
|
|
||||||
#### Удалено
|
|
||||||
- ❌ `frontend/src/components/AuthCallback.jsx` - не используется
|
|
||||||
|
|
||||||
### 3. Документация
|
|
||||||
|
|
||||||
#### `OPENID_CONNECT_SETUP.md`
|
|
||||||
- ✅ Полностью переписана для ZITADEL
|
|
||||||
- ✅ Пошаговая инструкция по настройке
|
|
||||||
- ✅ Примеры конфигурации
|
|
||||||
- ✅ Troubleshooting
|
|
||||||
|
|
||||||
#### `ZITADEL_QUICK_START.md` (новый)
|
|
||||||
- ✅ Краткая инструкция по быстрому старту
|
|
||||||
- ✅ 4 простых шага для настройки
|
|
||||||
- ✅ Решение типичных проблем
|
|
||||||
|
|
||||||
#### `OIDC_CHANGES.md` (этот файл)
|
|
||||||
- ✅ Резюме всех изменений
|
|
||||||
|
|
||||||
## Как использовать
|
|
||||||
|
|
||||||
### Для разработчика
|
|
||||||
|
|
||||||
1. Создайте приложение в ZITADEL
|
|
||||||
2. Скопируйте `backend/.env.example` в `backend/.env`
|
|
||||||
3. Заполните настройки ZITADEL
|
|
||||||
4. Запустите backend и frontend
|
|
||||||
5. Проверьте кнопку "Войти через ZITADEL"
|
|
||||||
|
|
||||||
### Для пользователя
|
|
||||||
|
|
||||||
1. Откройте страницу входа
|
|
||||||
2. Нажмите "Войти через ZITADEL"
|
|
||||||
3. Войдите в ZITADEL
|
|
||||||
4. Автоматически попадёте в панель
|
|
||||||
|
|
||||||
## Технические детали
|
|
||||||
|
|
||||||
### Поток аутентификации
|
|
||||||
|
|
||||||
```
|
|
||||||
1. Пользователь → Кнопка ZITADEL
|
|
||||||
2. Frontend → GET /api/auth/oidc/zitadel/login
|
|
||||||
3. Backend → Redirect на ZITADEL
|
|
||||||
4. ZITADEL → Пользователь вводит логин/пароль
|
|
||||||
5. ZITADEL → Redirect на /api/auth/oidc/zitadel/callback?code=...
|
|
||||||
6. Backend → Обмен code на access_token
|
|
||||||
7. Backend → Получение userinfo
|
|
||||||
8. Backend → Создание/обновление пользователя
|
|
||||||
9. Backend → Генерация JWT токена
|
|
||||||
10. Backend → Redirect на frontend?token=...&username=...
|
|
||||||
11. Frontend → Сохранение токена в localStorage
|
|
||||||
12. Frontend → Автоматический вход
|
|
||||||
```
|
|
||||||
|
|
||||||
### Структура пользователя OIDC
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"username": "john_doe",
|
|
||||||
"password": "",
|
|
||||||
"role": "user",
|
|
||||||
"servers": [],
|
|
||||||
"oidc_id": "zitadel:123456789012345678",
|
|
||||||
"email": "john@example.com",
|
|
||||||
"name": "John Doe",
|
|
||||||
"picture": "https://avatar.url",
|
|
||||||
"provider": "zitadel",
|
|
||||||
"created_at": "2026-01-15T12:00:00"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### API Endpoints
|
|
||||||
|
|
||||||
- `GET /api/auth/oidc/providers` - Список доступных провайдеров
|
|
||||||
- `GET /api/auth/oidc/zitadel/login` - Начало OAuth flow
|
|
||||||
- `GET /api/auth/oidc/zitadel/callback` - Обработка callback
|
|
||||||
|
|
||||||
## Зависимости
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- `authlib==1.3.0` - OAuth2/OIDC клиент
|
|
||||||
- `httpx==0.26.0` - HTTP клиент
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- `axios` - HTTP запросы (уже установлен)
|
|
||||||
|
|
||||||
## Безопасность
|
|
||||||
|
|
||||||
- ✅ PKCE (Proof Key for Code Exchange)
|
|
||||||
- ✅ State parameter для CSRF защиты
|
|
||||||
- ✅ Проверка redirect_uri
|
|
||||||
- ✅ JWT токены с истечением
|
|
||||||
- ✅ Хранение токенов в localStorage
|
|
||||||
|
|
||||||
## Что дальше?
|
|
||||||
|
|
||||||
### Возможные улучшения
|
|
||||||
- [ ] Добавить refresh token
|
|
||||||
- [ ] Добавить logout через ZITADEL
|
|
||||||
- [ ] Добавить отображение аватара пользователя
|
|
||||||
- [ ] Добавить связывание OIDC аккаунта с существующим
|
|
||||||
- [ ] Добавить управление сессиями
|
|
||||||
|
|
||||||
### Для продакшена
|
|
||||||
- [ ] Использовать HTTPS
|
|
||||||
- [ ] Настроить CORS правильно
|
|
||||||
- [ ] Добавить rate limiting
|
|
||||||
- [ ] Настроить логирование
|
|
||||||
- [ ] Добавить мониторинг
|
|
||||||
|
|
||||||
## Готово! ✅
|
|
||||||
|
|
||||||
OpenID Connect с ZITADEL полностью настроен и готов к использованию!
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
# 🔐 Настройка OpenID Connect
|
|
||||||
|
|
||||||
## Что добавлено
|
|
||||||
|
|
||||||
### Поддержка OpenID Connect провайдеров
|
|
||||||
MC Panel теперь поддерживает вход через ZITADEL:
|
|
||||||
- 🔐 **ZITADEL** - Современная платформа управления идентификацией и доступом
|
|
||||||
|
|
||||||
## 📋 Требования
|
|
||||||
|
|
||||||
### Установка зависимостей
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
pip install authlib==1.3.0 httpx==0.26.0
|
|
||||||
```
|
|
||||||
|
|
||||||
Или установите все зависимости:
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Новые зависимости:
|
|
||||||
- `authlib==1.3.0` - OAuth2/OpenID Connect клиент
|
|
||||||
- `httpx==0.26.0` - HTTP клиент для API запросов
|
|
||||||
|
|
||||||
**Важно:** В authlib 1.3.0 используется `authlib.integrations.starlette_client` (FastAPI основан на Starlette)
|
|
||||||
|
|
||||||
## ⚙️ Настройка ZITADEL
|
|
||||||
|
|
||||||
### 1. Создание проекта в ZITADEL
|
|
||||||
|
|
||||||
#### Шаг 1: Регистрация в ZITADEL
|
|
||||||
1. Перейдите на [ZITADEL Cloud](https://zitadel.cloud/) или используйте свой self-hosted инстанс
|
|
||||||
2. Создайте новый проект или используйте существующий
|
|
||||||
3. Запомните URL вашего инстанса (например: `https://your-instance.zitadel.cloud`)
|
|
||||||
|
|
||||||
#### Шаг 2: Создание приложения
|
|
||||||
1. В проекте нажмите "New Application"
|
|
||||||
2. Выберите тип: **Web Application**
|
|
||||||
3. Выберите метод аутентификации: **Code (with PKCE)**
|
|
||||||
4. Укажите название: `MC Panel`
|
|
||||||
|
|
||||||
#### Шаг 3: Настройка Redirect URIs
|
|
||||||
Добавьте следующие redirect URIs:
|
|
||||||
- Для разработки: `http://localhost:8000/api/auth/oidc/zitadel/callback`
|
|
||||||
- Для продакшена: `https://your-domain.com/api/auth/oidc/zitadel/callback`
|
|
||||||
|
|
||||||
#### Шаг 4: Получение учётных данных
|
|
||||||
После создания приложения вы получите:
|
|
||||||
- **Client ID** - идентификатор приложения
|
|
||||||
- **Client Secret** - секретный ключ (сохраните его!)
|
|
||||||
- **Issuer URL** - URL вашего ZITADEL инстанса
|
|
||||||
|
|
||||||
### 2. Настройка в .env
|
|
||||||
|
|
||||||
Создайте или отредактируйте файл `backend/.env`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Базовые настройки
|
|
||||||
SECRET_KEY=your-secret-key-here-change-this-in-production
|
|
||||||
BASE_URL=http://localhost:8000
|
|
||||||
FRONTEND_URL=http://localhost:3000
|
|
||||||
|
|
||||||
# ZITADEL Configuration
|
|
||||||
ZITADEL_ISSUER=https://your-instance.zitadel.cloud
|
|
||||||
ZITADEL_CLIENT_ID=your-client-id@your-project
|
|
||||||
ZITADEL_CLIENT_SECRET=your-client-secret
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Пример настройки
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Пример для ZITADEL Cloud
|
|
||||||
ZITADEL_ISSUER=https://mc-panel-abc123.zitadel.cloud
|
|
||||||
ZITADEL_CLIENT_ID=123456789012345678@mc-panel
|
|
||||||
ZITADEL_CLIENT_SECRET=abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGH
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Запуск
|
|
||||||
|
|
||||||
### 1. Создайте .env файл
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
cp .env.example .env
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Настройте ZITADEL
|
|
||||||
Отредактируйте `.env` файл:
|
|
||||||
```bash
|
|
||||||
# Базовые настройки
|
|
||||||
SECRET_KEY=your-secret-key-here-change-this-in-production
|
|
||||||
BASE_URL=http://localhost:8000
|
|
||||||
FRONTEND_URL=http://localhost:3000
|
|
||||||
|
|
||||||
# ZITADEL
|
|
||||||
ZITADEL_ISSUER=https://your-instance.zitadel.cloud
|
|
||||||
ZITADEL_CLIENT_ID=your-client-id@your-project
|
|
||||||
ZITADEL_CLIENT_SECRET=your-client-secret
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Запустите сервер
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Запустите фронтенд
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎨 Интерфейс
|
|
||||||
|
|
||||||
### Страница входа
|
|
||||||
- Обычная форма входа (логин/пароль)
|
|
||||||
- Разделитель "Или войдите через"
|
|
||||||
- Кнопка ZITADEL с иконкой 🔐 и фиолетовым цветом
|
|
||||||
- Автоматическое скрытие кнопки если ZITADEL не настроен
|
|
||||||
|
|
||||||
## 🔧 Как это работает
|
|
||||||
|
|
||||||
### 1. Пользователь нажимает кнопку провайдера
|
|
||||||
```
|
|
||||||
GET /api/auth/oidc/{provider}/login
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Перенаправление на провайдера
|
|
||||||
Пользователь перенаправляется на страницу авторизации провайдера
|
|
||||||
|
|
||||||
### 3. Callback от провайдера
|
|
||||||
```
|
|
||||||
GET /api/auth/oidc/{provider}/callback?code=...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Получение токена и данных пользователя
|
|
||||||
- Обмен code на access_token
|
|
||||||
- Получение информации о пользователе
|
|
||||||
- Создание или обновление пользователя в системе
|
|
||||||
|
|
||||||
### 5. Создание JWT токена
|
|
||||||
- Генерация JWT токена для пользователя
|
|
||||||
- Перенаправление на фронтенд с токеном
|
|
||||||
|
|
||||||
### 6. Автоматический вход
|
|
||||||
- Фронтенд получает токен из URL
|
|
||||||
- Сохраняет токен в localStorage
|
|
||||||
- Пользователь автоматически входит в систему
|
|
||||||
|
|
||||||
## 👥 Управление пользователями
|
|
||||||
|
|
||||||
### Автоматическое создание пользователей
|
|
||||||
При первом входе через OpenID Connect:
|
|
||||||
- Создаётся новый пользователь
|
|
||||||
- Роль: "user" (обычный пользователь)
|
|
||||||
- Username генерируется из email или имени
|
|
||||||
- Сохраняется связь с провайдером
|
|
||||||
|
|
||||||
### Данные пользователя
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"username": "john_doe",
|
|
||||||
"password": "",
|
|
||||||
"role": "user",
|
|
||||||
"servers": [],
|
|
||||||
"oidc_id": "zitadel:123456789012345678",
|
|
||||||
"email": "john@example.com",
|
|
||||||
"name": "John Doe",
|
|
||||||
"picture": "https://avatar.url",
|
|
||||||
"provider": "zitadel",
|
|
||||||
"created_at": "2026-01-15T12:00:00"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Повторные входы
|
|
||||||
- Пользователь находится по `oidc_id`
|
|
||||||
- Обновляется email, имя и аватар
|
|
||||||
- Роль и серверы сохраняются
|
|
||||||
|
|
||||||
## 🔐 Безопасность
|
|
||||||
|
|
||||||
### Проверки
|
|
||||||
- Проверка state параметра (CSRF защита)
|
|
||||||
- Проверка redirect_uri
|
|
||||||
- Валидация токенов от провайдеров
|
|
||||||
- Проверка подписи JWT токенов
|
|
||||||
|
|
||||||
### Рекомендации
|
|
||||||
- Используйте HTTPS в продакшене
|
|
||||||
- Регулярно обновляйте client secrets
|
|
||||||
- Ограничьте redirect URIs
|
|
||||||
- Мониторьте подозрительную активность
|
|
||||||
|
|
||||||
## 🚨 Troubleshooting
|
|
||||||
|
|
||||||
### Ошибка "Provider not found"
|
|
||||||
- Проверьте настройки в .env файле
|
|
||||||
- Убедитесь что CLIENT_ID указан
|
|
||||||
- Перезапустите сервер
|
|
||||||
|
|
||||||
### Ошибка "Invalid redirect_uri"
|
|
||||||
- Проверьте redirect URI в настройках провайдера
|
|
||||||
- Должен точно совпадать с `BASE_URL/api/auth/oidc/{provider}/callback`
|
|
||||||
|
|
||||||
### Ошибка "Invalid client"
|
|
||||||
- Проверьте CLIENT_ID и CLIENT_SECRET
|
|
||||||
- Убедитесь что приложение активно у провайдера
|
|
||||||
|
|
||||||
### Пользователь не создаётся
|
|
||||||
- Проверьте логи сервера
|
|
||||||
- Убедитесь что провайдер возвращает email
|
|
||||||
- Проверьте права на запись в users.json
|
|
||||||
|
|
||||||
## ✅ Готово!
|
|
||||||
|
|
||||||
OpenID Connect с ZITADEL настроен и готов к использованию. Пользователи могут входить через:
|
|
||||||
- Обычную форму (логин/пароль)
|
|
||||||
- ZITADEL (OpenID Connect)
|
|
||||||
|
|
||||||
### Тестирование
|
|
||||||
1. Настройте ZITADEL в .env файле
|
|
||||||
2. Перезапустите сервер
|
|
||||||
3. Откройте страницу входа
|
|
||||||
4. Увидите кнопку "Войти через ZITADEL"
|
|
||||||
5. Нажмите на кнопку и войдите через ZITADEL
|
|
||||||
|
|
||||||
### Преимущества ZITADEL
|
|
||||||
- ✅ Полная поддержка OpenID Connect
|
|
||||||
- ✅ Современный интерфейс управления
|
|
||||||
- ✅ Поддержка многофакторной аутентификации
|
|
||||||
- ✅ Self-hosted или Cloud решение
|
|
||||||
- ✅ Бесплатный план для небольших проектов
|
|
||||||
|
|
||||||
**Удобного использования! 🔐**
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
# 👤 Система личного кабинета
|
|
||||||
|
|
||||||
## Что добавлено
|
|
||||||
|
|
||||||
### ✅ Новые возможности
|
|
||||||
|
|
||||||
1. **Личный кабинет** - полноценная система управления профилем
|
|
||||||
2. **Три вкладки**:
|
|
||||||
- 📊 **Обзор** - статистика и информация о профиле
|
|
||||||
- 👤 **Имя пользователя** - изменение имени пользователя
|
|
||||||
- 🔒 **Пароль** - изменение пароля
|
|
||||||
|
|
||||||
3. **Кнопка "Личный кабинет"** в header рядом с кнопкой "Тикеты"
|
|
||||||
|
|
||||||
### 📊 Вкладка "Обзор"
|
|
||||||
|
|
||||||
#### Информация о пользователе
|
|
||||||
- Имя пользователя
|
|
||||||
- Роль (Администратор, Тех. поддержка, Пользователь)
|
|
||||||
- Цветной бейдж роли
|
|
||||||
|
|
||||||
#### Статистика
|
|
||||||
- **Серверы**:
|
|
||||||
- Всего серверов
|
|
||||||
- Мои серверы (владелец)
|
|
||||||
- Доступные серверы
|
|
||||||
|
|
||||||
- **Тикеты**:
|
|
||||||
- Всего тикетов
|
|
||||||
- На рассмотрении
|
|
||||||
- В работе
|
|
||||||
- Закрыто
|
|
||||||
|
|
||||||
- **Роль**:
|
|
||||||
- Название роли
|
|
||||||
- Описание прав
|
|
||||||
|
|
||||||
#### Список серверов
|
|
||||||
- Отображение всех серверов пользователя
|
|
||||||
- Название и ID сервера
|
|
||||||
- Красивые карточки
|
|
||||||
|
|
||||||
### 👤 Вкладка "Имя пользователя"
|
|
||||||
|
|
||||||
#### Возможности
|
|
||||||
- Просмотр текущего имени
|
|
||||||
- Ввод нового имени (минимум 3 символа)
|
|
||||||
- Подтверждение паролем
|
|
||||||
- Автоматический перелогин с новым именем
|
|
||||||
|
|
||||||
#### Безопасность
|
|
||||||
- Проверка уникальности имени
|
|
||||||
- Проверка пароля
|
|
||||||
- Обновление владельцев серверов
|
|
||||||
- Обновление доступов к серверам
|
|
||||||
- Новый JWT токен
|
|
||||||
|
|
||||||
### 🔒 Вкладка "Пароль"
|
|
||||||
|
|
||||||
#### Возможности
|
|
||||||
- Ввод текущего пароля
|
|
||||||
- Ввод нового пароля (минимум 6 символов)
|
|
||||||
- Подтверждение нового пароля
|
|
||||||
- Показ/скрытие паролей
|
|
||||||
|
|
||||||
#### Безопасность
|
|
||||||
- Проверка текущего пароля
|
|
||||||
- Проверка совпадения новых паролей
|
|
||||||
- Хеширование пароля (bcrypt)
|
|
||||||
|
|
||||||
## 🚀 Как использовать
|
|
||||||
|
|
||||||
### Открытие личного кабинета
|
|
||||||
1. Нажмите кнопку "Личный кабинет" в header
|
|
||||||
2. Откроется страница с тремя вкладками
|
|
||||||
|
|
||||||
### Просмотр статистики
|
|
||||||
1. Откройте вкладку "Обзор"
|
|
||||||
2. Посмотрите информацию о профиле
|
|
||||||
3. Посмотрите статистику по серверам и тикетам
|
|
||||||
4. Посмотрите список своих серверов
|
|
||||||
|
|
||||||
### Изменение имени пользователя
|
|
||||||
1. Откройте вкладку "Имя пользователя"
|
|
||||||
2. Введите новое имя (минимум 3 символа)
|
|
||||||
3. Введите текущий пароль для подтверждения
|
|
||||||
4. Нажмите "Изменить имя пользователя"
|
|
||||||
5. Вы будете автоматически перелогинены
|
|
||||||
|
|
||||||
⚠️ **Важно**: После изменения имени:
|
|
||||||
- Обновятся все серверы, где вы владелец
|
|
||||||
- Обновятся все доступы к серверам
|
|
||||||
- Вы получите новый токен авторизации
|
|
||||||
|
|
||||||
### Изменение пароля
|
|
||||||
1. Откройте вкладку "Пароль"
|
|
||||||
2. Введите текущий пароль
|
|
||||||
3. Введите новый пароль (минимум 6 символов)
|
|
||||||
4. Подтвердите новый пароль
|
|
||||||
5. Нажмите "Изменить пароль"
|
|
||||||
|
|
||||||
⚠️ **Важно**: После изменения пароля используйте новый пароль для входа.
|
|
||||||
|
|
||||||
## 📁 Новые файлы
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- Добавлены endpoints в `backend/main.py`:
|
|
||||||
- `PUT /api/profile/username` - изменить имя пользователя
|
|
||||||
- `PUT /api/profile/password` - изменить пароль
|
|
||||||
- `GET /api/profile/stats` - получить статистику профиля
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- `frontend/src/components/Profile.jsx` - компонент личного кабинета
|
|
||||||
|
|
||||||
## 🎨 Интерфейс
|
|
||||||
|
|
||||||
### Вкладки
|
|
||||||
- Современный дизайн с вкладками
|
|
||||||
- Плавные переходы между вкладками
|
|
||||||
- Адаптивный дизайн
|
|
||||||
|
|
||||||
### Карточки статистики
|
|
||||||
- Цветные иконки
|
|
||||||
- Числовые показатели
|
|
||||||
- Детальная информация
|
|
||||||
|
|
||||||
### Формы
|
|
||||||
- Валидация полей
|
|
||||||
- Показ/скрытие паролей
|
|
||||||
- Предупреждения о последствиях
|
|
||||||
- Кнопки с индикацией загрузки
|
|
||||||
|
|
||||||
## 🔧 Технические детали
|
|
||||||
|
|
||||||
### API Endpoints
|
|
||||||
|
|
||||||
#### PUT /api/profile/username
|
|
||||||
Изменить имя пользователя
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"new_username": "NewUsername",
|
|
||||||
"password": "current_password"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Имя пользователя изменено",
|
|
||||||
"access_token": "new_jwt_token",
|
|
||||||
"username": "NewUsername"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### PUT /api/profile/password
|
|
||||||
Изменить пароль
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"old_password": "old_password",
|
|
||||||
"new_password": "new_password"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Пароль изменён"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### GET /api/profile/stats
|
|
||||||
Получить статистику профиля
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"username": "username",
|
|
||||||
"role": "user",
|
|
||||||
"owned_servers": [
|
|
||||||
{
|
|
||||||
"name": "server1",
|
|
||||||
"displayName": "My Server"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"accessible_servers": [],
|
|
||||||
"tickets": {
|
|
||||||
"total": 5,
|
|
||||||
"pending": 2,
|
|
||||||
"in_progress": 1,
|
|
||||||
"closed": 2
|
|
||||||
},
|
|
||||||
"total_servers": 1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Безопасность
|
|
||||||
|
|
||||||
#### Изменение имени пользователя
|
|
||||||
1. Проверка пароля
|
|
||||||
2. Проверка уникальности нового имени
|
|
||||||
3. Обновление владельцев серверов
|
|
||||||
4. Обновление доступов к серверам
|
|
||||||
5. Создание нового JWT токена
|
|
||||||
|
|
||||||
#### Изменение пароля
|
|
||||||
1. Проверка текущего пароля
|
|
||||||
2. Проверка длины нового пароля (минимум 6 символов)
|
|
||||||
3. Хеширование нового пароля (bcrypt)
|
|
||||||
|
|
||||||
## ✅ Готово!
|
|
||||||
|
|
||||||
Система личного кабинета полностью интегрирована в MC Panel. Пользователи могут:
|
|
||||||
- Просматривать статистику своего профиля
|
|
||||||
- Изменять имя пользователя
|
|
||||||
- Изменять пароль
|
|
||||||
- Видеть свои серверы и тикеты
|
|
||||||
|
|
||||||
### Доступ к личному кабинету
|
|
||||||
Кнопка "Личный кабинет" доступна всем пользователям в header рядом с кнопкой "Тикеты".
|
|
||||||
|
|
||||||
### Учётные данные по умолчанию
|
|
||||||
- **Логин**: Sofa12345
|
|
||||||
- **Пароль**: arkonsad123
|
|
||||||
- **Роль**: admin
|
|
||||||
201
QUICK_START.md
201
QUICK_START.md
@@ -1,201 +0,0 @@
|
|||||||
# 🚀 Быстрый старт MC Panel
|
|
||||||
|
|
||||||
## Первый запуск
|
|
||||||
|
|
||||||
### Вариант 1: Автоматический запуск (Windows)
|
|
||||||
|
|
||||||
Просто запустите файл:
|
|
||||||
```
|
|
||||||
START_PANEL.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
Откроются два окна:
|
|
||||||
- **MC Panel Backend** - бэкенд сервер
|
|
||||||
- **MC Panel Frontend** - фронтенд сервер
|
|
||||||
|
|
||||||
Подождите 10-15 секунд и откройте в браузере:
|
|
||||||
```
|
|
||||||
http://localhost:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
### Вариант 2: Ручной запуск
|
|
||||||
|
|
||||||
**Терминал 1 - Бэкенд:**
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**Терминал 2 - Фронтенд:**
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание первого сервера
|
|
||||||
|
|
||||||
1. Нажмите кнопку **"+"** в левой панели
|
|
||||||
2. Заполните форму:
|
|
||||||
- **Имя папки**: `my_server` (только латиница)
|
|
||||||
- **Отображаемое имя**: `Мой сервер`
|
|
||||||
- **Команда запуска**: `java -Xmx2G -Xms1G -jar server.jar nogui`
|
|
||||||
3. Нажмите **"Создать"**
|
|
||||||
|
|
||||||
## Загрузка файлов сервера
|
|
||||||
|
|
||||||
1. Выберите созданный сервер в списке
|
|
||||||
2. Перейдите на вкладку **"Файлы"**
|
|
||||||
3. Нажмите **"Загрузить"**
|
|
||||||
4. Выберите `server.jar` (скачайте с официального сайта Minecraft)
|
|
||||||
5. Если нужно, создайте файл `eula.txt`:
|
|
||||||
- Нажмите **"Загрузить"**
|
|
||||||
- Создайте текстовый файл с содержимым: `eula=true`
|
|
||||||
- Загрузите его
|
|
||||||
|
|
||||||
## Запуск сервера
|
|
||||||
|
|
||||||
1. Нажмите кнопку **"Старт"** на карточке сервера
|
|
||||||
2. Перейдите на вкладку **"Консоль"** чтобы видеть логи
|
|
||||||
3. Дождитесь сообщения `Done!` в консоли
|
|
||||||
4. Сервер готов к подключению!
|
|
||||||
|
|
||||||
## Управление сервером
|
|
||||||
|
|
||||||
### Консоль
|
|
||||||
- Просмотр логов в реальном времени
|
|
||||||
- Отправка команд серверу
|
|
||||||
- Примеры команд: `list`, `say Hello`, `stop`
|
|
||||||
|
|
||||||
### Файлы
|
|
||||||
- Просмотр и редактирование конфигов
|
|
||||||
- Загрузка плагинов/модов
|
|
||||||
- Скачивание файлов
|
|
||||||
- Переименование и удаление
|
|
||||||
|
|
||||||
### Статистика
|
|
||||||
- Использование CPU
|
|
||||||
- Потребление ОЗУ
|
|
||||||
- Размер на диске
|
|
||||||
- Статус сервера
|
|
||||||
|
|
||||||
### Настройки
|
|
||||||
- Изменение отображаемого имени
|
|
||||||
- Настройка команды запуска
|
|
||||||
- Удаление сервера
|
|
||||||
|
|
||||||
## Доступ через сеть (Radmin VPN)
|
|
||||||
|
|
||||||
### На вашем компьютере:
|
|
||||||
|
|
||||||
1. Узнайте ваш IP в Radmin VPN:
|
|
||||||
```bash
|
|
||||||
ipconfig
|
|
||||||
```
|
|
||||||
Ищите адаптер Radmin VPN (обычно `26.x.x.x`)
|
|
||||||
|
|
||||||
2. Запустите панель как обычно
|
|
||||||
|
|
||||||
3. Откройте в браузере:
|
|
||||||
```
|
|
||||||
http://localhost:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
### На компьютере друга:
|
|
||||||
|
|
||||||
1. Откройте в браузере:
|
|
||||||
```
|
|
||||||
http://ВАШ_RADMIN_IP:3000
|
|
||||||
```
|
|
||||||
Например: `http://26.62.117.104:3000`
|
|
||||||
|
|
||||||
2. Панель автоматически подключится к вашему бэкенду
|
|
||||||
|
|
||||||
### Если не работает:
|
|
||||||
|
|
||||||
Откройте порты в брандмауэре Windows (от имени администратора):
|
|
||||||
```powershell
|
|
||||||
netsh advfirewall firewall add rule name="MC Panel Backend" dir=in action=allow protocol=TCP localport=8000
|
|
||||||
netsh advfirewall firewall add rule name="MC Panel Frontend" dir=in action=allow protocol=TCP localport=3000
|
|
||||||
```
|
|
||||||
|
|
||||||
## Типичные проблемы
|
|
||||||
|
|
||||||
### Java не найдена
|
|
||||||
|
|
||||||
**Ошибка:** `'java' is not recognized...`
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Установите Java 17+: https://adoptium.net/
|
|
||||||
2. Перезапустите терминал
|
|
||||||
3. Проверьте: `java -version`
|
|
||||||
|
|
||||||
### Сервер не запускается
|
|
||||||
|
|
||||||
**Причины:**
|
|
||||||
- Отсутствует `server.jar`
|
|
||||||
- Не принят EULA
|
|
||||||
- Неправильная команда запуска
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Проверьте наличие `server.jar` в файлах
|
|
||||||
2. Создайте `eula.txt` с содержимым `eula=true`
|
|
||||||
3. Проверьте команду запуска в настройках
|
|
||||||
|
|
||||||
### Порт уже занят
|
|
||||||
|
|
||||||
**Ошибка:** `Address already in use`
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
```bash
|
|
||||||
# Найти процесс на порту 8000
|
|
||||||
netstat -ano | findstr :8000
|
|
||||||
|
|
||||||
# Убить процесс (замените PID)
|
|
||||||
taskkill /F /PID <PID>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Не видно файлов/настроек
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Откройте консоль браузера (F12)
|
|
||||||
2. Проверьте вкладку Network на ошибки
|
|
||||||
3. Обновите страницу (F5)
|
|
||||||
4. Перезапустите бэкенд
|
|
||||||
|
|
||||||
## Полезные ссылки
|
|
||||||
|
|
||||||
- **Скачать Minecraft Server**: https://www.minecraft.net/en-us/download/server
|
|
||||||
- **Документация Minecraft**: https://minecraft.fandom.com/wiki/Server
|
|
||||||
- **Java Download**: https://adoptium.net/
|
|
||||||
- **Radmin VPN**: https://www.radmin-vpn.com/
|
|
||||||
|
|
||||||
## Команды Minecraft
|
|
||||||
|
|
||||||
Полезные команды для консоли:
|
|
||||||
|
|
||||||
```
|
|
||||||
list # Список игроков
|
|
||||||
say <message> # Сообщение всем
|
|
||||||
kick <player> # Кикнуть игрока
|
|
||||||
ban <player> # Забанить игрока
|
|
||||||
op <player> # Дать права оператора
|
|
||||||
deop <player> # Забрать права оператора
|
|
||||||
whitelist add <player> # Добавить в белый список
|
|
||||||
stop # Остановить сервер
|
|
||||||
```
|
|
||||||
|
|
||||||
## Конфигурационные файлы
|
|
||||||
|
|
||||||
Основные файлы для редактирования:
|
|
||||||
|
|
||||||
- **server.properties** - основные настройки сервера
|
|
||||||
- **eula.txt** - принятие лицензии
|
|
||||||
- **ops.json** - список операторов
|
|
||||||
- **whitelist.json** - белый список игроков
|
|
||||||
- **banned-players.json** - забаненные игроки
|
|
||||||
|
|
||||||
Редактируйте их через вкладку "Файлы" в панели!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Готово!** Теперь у вас есть полноценная панель управления Minecraft серверами! 🎮
|
|
||||||
276
README.md
276
README.md
@@ -1,128 +1,192 @@
|
|||||||
# MC Panel - Панель управления Minecraft серверами
|
# MC Panel - Панель управления Minecraft серверами
|
||||||
|
|
||||||
Панель управления для Minecraft серверов с FastAPI бэкендом и React фронтендом.
|
**Версия:** 1.0.0
|
||||||
|
**Дата:** 15 января 2026
|
||||||
|
|
||||||
## Возможности
|
---
|
||||||
|
|
||||||
- ➕ Создание новых серверов
|
## 📚 Документация
|
||||||
- 🎮 Запуск и остановка серверов
|
|
||||||
- 💻 Консоль с отправкой команд в реальном времени
|
|
||||||
- 📁 Менеджер файлов:
|
|
||||||
- Загрузка и скачивание файлов
|
|
||||||
- Просмотр содержимого файлов
|
|
||||||
- Редактирование текстовых файлов
|
|
||||||
- Переименование файлов и папок
|
|
||||||
- Удаление файлов и папок
|
|
||||||
- 📊 Мониторинг ресурсов (CPU, ОЗУ, диск)
|
|
||||||
- ⚙️ Настройки сервера (название, команда запуска)
|
|
||||||
- 🗑️ Удаление серверов
|
|
||||||
- 🔄 Автообновление статистики
|
|
||||||
|
|
||||||
## Установка
|
### 📖 [ДОКУМЕНТАЦИЯ.md](ДОКУМЕНТАЦИЯ.md)
|
||||||
|
**Полная документация проекта**
|
||||||
|
|
||||||
### Бэкенд
|
Содержит всю информацию о проекте:
|
||||||
|
- 🚀 Быстрый старт
|
||||||
|
- ⚙️ Установка и настройка
|
||||||
|
- 🎮 Функциональность
|
||||||
|
- 🔔 Система уведомлений
|
||||||
|
- 🎨 Дизайн и темы
|
||||||
|
- 📁 Файловый менеджер
|
||||||
|
- 🎫 Система тикетов
|
||||||
|
- 👤 Личный кабинет
|
||||||
|
- 🔐 OpenID Connect
|
||||||
|
- 👥 Роли пользователей
|
||||||
|
- 🔒 Безопасность
|
||||||
|
- 🔧 Troubleshooting
|
||||||
|
|
||||||
|
**Начните отсюда!** 👈
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🌐 [API.md](API.md)
|
||||||
|
**Документация API**
|
||||||
|
|
||||||
|
Полное описание REST API:
|
||||||
|
- 📋 Все эндпоинты (37 шт.)
|
||||||
|
- 🔐 Аутентификация
|
||||||
|
- 👥 Управление пользователями
|
||||||
|
- 🖥️ Управление серверами
|
||||||
|
- 📁 Управление файлами
|
||||||
|
- 🎫 Тикеты
|
||||||
|
- 💡 Примеры интеграции (Python, JavaScript, cURL)
|
||||||
|
- 📦 Postman коллекция
|
||||||
|
|
||||||
|
**Для разработчиков!** 👨💻
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📦 [MC_Panel_API.postman_collection.json](MC_Panel_API.postman_collection.json)
|
||||||
|
**Postman коллекция**
|
||||||
|
|
||||||
|
Готовая коллекция для тестирования API:
|
||||||
|
- 40+ готовых запросов
|
||||||
|
- Автоматическое сохранение токена
|
||||||
|
- Переменные окружения
|
||||||
|
- Примеры тел запросов
|
||||||
|
|
||||||
|
**Импортируйте в Postman!** 📮
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
### 1. Установка
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
cd backend
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
python main.py
|
python main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Сервер запустится на http://0.0.0.0:8000
|
**Frontend:**
|
||||||
|
|
||||||
### Фронтенд
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
npm install
|
npm install
|
||||||
npm run dev -- --host
|
|
||||||
```
|
|
||||||
|
|
||||||
Приложение откроется на http://localhost:3000
|
|
||||||
|
|
||||||
## Доступ через сеть (Radmin VPN, Hamachi и т.д.)
|
|
||||||
|
|
||||||
### Вариант 1: Автоматическое определение (рекомендуется)
|
|
||||||
|
|
||||||
Фронтенд автоматически определит IP адрес и подключится к бэкенду.
|
|
||||||
|
|
||||||
1. Запустите бэкенд (он слушает на всех интерфейсах)
|
|
||||||
2. Запустите фронтенд с флагом `--host`:
|
|
||||||
```bash
|
|
||||||
npm run dev -- --host
|
|
||||||
```
|
|
||||||
3. Откройте в браузере: `http://ВАШ_IP:3000`
|
|
||||||
- Например: `http://26.123.45.67:3000` (Radmin VPN IP)
|
|
||||||
|
|
||||||
### Вариант 2: Ручная настройка
|
|
||||||
|
|
||||||
Создайте файл `frontend/.env.local`:
|
|
||||||
```
|
|
||||||
VITE_API_URL=http://26.123.45.67:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
Замените `26.123.45.67` на ваш IP адрес в Radmin VPN.
|
|
||||||
|
|
||||||
### Проверка IP адреса
|
|
||||||
|
|
||||||
Windows:
|
|
||||||
```bash
|
|
||||||
ipconfig
|
|
||||||
```
|
|
||||||
|
|
||||||
Ищите адрес адаптера Radmin VPN (обычно начинается с 26.x.x.x)
|
|
||||||
|
|
||||||
## Структура
|
|
||||||
|
|
||||||
```
|
|
||||||
backend/
|
|
||||||
main.py # FastAPI сервер
|
|
||||||
requirements.txt # Зависимости Python
|
|
||||||
servers/ # Папка с серверами Minecraft
|
|
||||||
|
|
||||||
frontend/
|
|
||||||
src/
|
|
||||||
components/
|
|
||||||
Console.jsx # Компонент консоли
|
|
||||||
FileManager.jsx # Менеджер файлов
|
|
||||||
Stats.jsx # Статистика
|
|
||||||
App.jsx # Главный компонент
|
|
||||||
main.jsx # Точка входа
|
|
||||||
package.json # Зависимости Node.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
### Быстрый старт
|
|
||||||
|
|
||||||
**Windows:**
|
|
||||||
```bash
|
|
||||||
START_PANEL.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
**Вручную:**
|
|
||||||
```bash
|
|
||||||
# Терминал 1 - Бэкенд
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
|
|
||||||
# Терминал 2 - Фронтенд
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Откройте в браузере: http://localhost:3000
|
### 2. Первый вход
|
||||||
|
|
||||||
### Создание сервера
|
1. Откройте `http://localhost:3000`
|
||||||
|
2. Зарегистрируйтесь (первый пользователь = admin)
|
||||||
|
3. Создайте сервер
|
||||||
|
4. Загрузите `server.jar`
|
||||||
|
5. Запустите сервер!
|
||||||
|
|
||||||
1. Нажмите кнопку "+" для создания нового сервера
|
**Учетные данные по умолчанию:**
|
||||||
2. Укажите имя, отображаемое название и команду запуска
|
- Логин: `Root`
|
||||||
3. Загрузите файлы сервера (server.jar и т.д.) через менеджер файлов
|
- Пароль: `Admin`
|
||||||
4. Создайте файл `eula.txt` с содержимым `eula=true`
|
|
||||||
5. Запустите сервер и управляйте им через вкладки:
|
|
||||||
- **Консоль** - просмотр логов и отправка команд
|
|
||||||
- **Файлы** - управление файлами сервера
|
|
||||||
- **Статистика** - мониторинг ресурсов
|
|
||||||
- **Настройки** - изменение параметров сервера
|
|
||||||
|
|
||||||
Подробнее: см. `QUICK_START.md`
|
---
|
||||||
|
|
||||||
|
## ✨ Основные возможности
|
||||||
|
|
||||||
|
- 🖥️ **Управление серверами** - запуск, остановка, мониторинг
|
||||||
|
- 📁 **Файловый менеджер** - полное управление файлами
|
||||||
|
- 💬 **Консоль** - команды и логи в реальном времени
|
||||||
|
- 📊 **Статистика** - CPU, RAM, диск
|
||||||
|
- 🎫 **Тикеты** - система поддержки
|
||||||
|
- 👥 **Пользователи** - роли и права доступа
|
||||||
|
- 🔐 **OpenID Connect** - интеграция с ZITADEL
|
||||||
|
- 🎨 **6 тем** - включая современную темную
|
||||||
|
- 🔔 **Уведомления** - о всех событиях
|
||||||
|
- 👤 **Личный кабинет** - профиль и статистика
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Технологии
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- FastAPI (Python)
|
||||||
|
- JWT аутентификация
|
||||||
|
- WebSocket
|
||||||
|
- Authlib (OpenID Connect)
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- React 18
|
||||||
|
- Tailwind CSS
|
||||||
|
- Axios
|
||||||
|
- Lucide React
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
mc-panel/
|
||||||
|
├── backend/
|
||||||
|
│ ├── main.py # FastAPI приложение
|
||||||
|
│ ├── oidc_config.py # OpenID Connect
|
||||||
|
│ ├── requirements.txt # Зависимости
|
||||||
|
│ └── servers/ # Папка серверов
|
||||||
|
├── frontend/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── App.jsx # Главный компонент
|
||||||
|
│ │ ├── components/ # React компоненты
|
||||||
|
│ │ └── themes.js # Темы
|
||||||
|
│ └── package.json # npm зависимости
|
||||||
|
├── ДОКУМЕНТАЦИЯ.md # Документация проекта
|
||||||
|
├── API.md # API документация
|
||||||
|
├── MC_Panel_API.postman_collection.json # Postman
|
||||||
|
└── README.md # Этот файл
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 Безопасность
|
||||||
|
|
||||||
|
- JWT токены (7 дней)
|
||||||
|
- Bcrypt хеширование паролей
|
||||||
|
- Проверка прав доступа
|
||||||
|
- Защита файловой системы
|
||||||
|
- OpenID Connect поддержка
|
||||||
|
|
||||||
|
**Для production:**
|
||||||
|
1. Измените `SECRET_KEY` в `backend/main.py`
|
||||||
|
2. Используйте HTTPS
|
||||||
|
3. Настройте CORS
|
||||||
|
4. Используйте базу данных вместо JSON
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Поддержка
|
||||||
|
|
||||||
|
- **Документация:** [ДОКУМЕНТАЦИЯ.md](ДОКУМЕНТАЦИЯ.md)
|
||||||
|
- **API:** [API.md](API.md)
|
||||||
|
- **Тикеты:** Используйте систему тикетов в панели
|
||||||
|
- **GitHub:** [Ссылка на репозиторий]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Лицензия
|
||||||
|
|
||||||
|
MIT License - свободное использование
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🙏 Благодарности
|
||||||
|
|
||||||
|
Спасибо за использование MC Panel!
|
||||||
|
|
||||||
|
Если у вас есть вопросы или предложения:
|
||||||
|
1. Прочитайте документацию
|
||||||
|
2. Проверьте API документацию
|
||||||
|
3. Создайте тикет в системе
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Версия:** 1.0.0
|
||||||
|
**Дата:** 15 января 2026
|
||||||
|
|
||||||
|
**Приятного использования!** 🎮
|
||||||
|
|||||||
138
README_FINAL.md
138
README_FINAL.md
@@ -1,138 +0,0 @@
|
|||||||
# MC Panel - Финальная версия с авторизацией
|
|
||||||
|
|
||||||
## ✅ Что готово
|
|
||||||
|
|
||||||
Полноценная панель управления Minecraft серверами с системой авторизации и управлением пользователями.
|
|
||||||
|
|
||||||
## 🚀 Быстрый старт
|
|
||||||
|
|
||||||
### 1. Переименуйте файлы
|
|
||||||
|
|
||||||
**ВАЖНО! Сделайте это вручную в проводнике Windows:**
|
|
||||||
|
|
||||||
1. `backend/main_new.py` → `backend/main.py`
|
|
||||||
2. `frontend/src/App_final.jsx` → `frontend/src/App.jsx`
|
|
||||||
|
|
||||||
### 2. Установите зависимости
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Запустите
|
|
||||||
|
|
||||||
```bash
|
|
||||||
START_PANEL.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
Или вручную:
|
|
||||||
```bash
|
|
||||||
# Терминал 1
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
|
|
||||||
# Терминал 2
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Войдите
|
|
||||||
|
|
||||||
Откройте http://localhost:3000
|
|
||||||
|
|
||||||
- Логин: `admin`
|
|
||||||
- Пароль: `admin`
|
|
||||||
|
|
||||||
## 📚 Документация
|
|
||||||
|
|
||||||
- **INSTALLATION_COMPLETE.md** - полная инструкция по установке
|
|
||||||
- **AUTH_SETUP.md** - руководство по авторизации
|
|
||||||
- **FINAL_STEPS.md** - пошаговые инструкции
|
|
||||||
- **QUICK_START.md** - быстрый старт для новичков
|
|
||||||
- **DEBUG_GUIDE.md** - решение проблем
|
|
||||||
- **NETWORK_SETUP.md** - настройка удаленного доступа
|
|
||||||
|
|
||||||
## 🎯 Основные функции
|
|
||||||
|
|
||||||
### Авторизация
|
|
||||||
- Вход и регистрация
|
|
||||||
- JWT токены
|
|
||||||
- Автоматический выход при истечении
|
|
||||||
|
|
||||||
### Роли
|
|
||||||
- **Администратор** - полный доступ
|
|
||||||
- **Пользователь** - доступ к назначенным серверам
|
|
||||||
|
|
||||||
### Управление пользователями
|
|
||||||
- Выдача/отзыв доступа к серверам
|
|
||||||
- Изменение ролей
|
|
||||||
- Удаление пользователей
|
|
||||||
|
|
||||||
### Управление серверами
|
|
||||||
- Создание/удаление серверов
|
|
||||||
- Запуск/остановка
|
|
||||||
- Консоль в реальном времени
|
|
||||||
- Файловый менеджер с редактором
|
|
||||||
- Мониторинг ресурсов
|
|
||||||
- Настройки
|
|
||||||
|
|
||||||
## ⚠️ Важно
|
|
||||||
|
|
||||||
### Смените секретный ключ!
|
|
||||||
|
|
||||||
Откройте `backend/main.py` и измените:
|
|
||||||
```python
|
|
||||||
SECRET_KEY = "your-secret-key-change-this-in-production-12345"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Смените пароль admin
|
|
||||||
|
|
||||||
1. Создайте нового администратора
|
|
||||||
2. Войдите под ним
|
|
||||||
3. Удалите старого admin
|
|
||||||
|
|
||||||
## 🌐 Удаленный доступ
|
|
||||||
|
|
||||||
1. Узнайте IP: `ipconfig` (Radmin VPN обычно 26.x.x.x)
|
|
||||||
2. Друг открывает: `http://ВАШ_IP:3000`
|
|
||||||
3. Друг регистрируется
|
|
||||||
4. Вы выдаете ему доступ к серверам
|
|
||||||
|
|
||||||
## 📁 Структура
|
|
||||||
|
|
||||||
```
|
|
||||||
mc-panel/
|
|
||||||
├── backend/
|
|
||||||
│ ├── main_new.py → main.py # Переименуйте!
|
|
||||||
│ ├── requirements.txt
|
|
||||||
│ ├── users.json # Создастся автоматически
|
|
||||||
│ └── servers/
|
|
||||||
└── frontend/
|
|
||||||
├── src/
|
|
||||||
│ ├── App_final.jsx → App.jsx # Переименуйте!
|
|
||||||
│ └── components/
|
|
||||||
│ ├── Auth.jsx
|
|
||||||
│ ├── Users.jsx
|
|
||||||
│ └── ...
|
|
||||||
└── package.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🆘 Помощь
|
|
||||||
|
|
||||||
### Не могу войти
|
|
||||||
Удалите `backend/users.json` и перезапустите бэкенд.
|
|
||||||
|
|
||||||
### Ошибка импорта
|
|
||||||
```bash
|
|
||||||
pip install passlib[bcrypt] python-jose[cryptography]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Пользователь не видит серверы
|
|
||||||
Админ должен выдать доступ в разделе "Пользователи".
|
|
||||||
|
|
||||||
## 🎉 Готово!
|
|
||||||
|
|
||||||
Теперь у вас полноценная панель с авторизацией!
|
|
||||||
|
|
||||||
Подробнее см. **INSTALLATION_COMPLETE.md**
|
|
||||||
228
README_OIDC.md
228
README_OIDC.md
@@ -1,228 +0,0 @@
|
|||||||
# 🔐 OpenID Connect с ZITADEL - Документация
|
|
||||||
|
|
||||||
## 🎯 Статус: ✅ Готово к использованию
|
|
||||||
|
|
||||||
OpenID Connect с ZITADEL полностью интегрирован в MC Panel!
|
|
||||||
|
|
||||||
## 📚 Документация
|
|
||||||
|
|
||||||
### 🚀 Начало работы
|
|
||||||
Выберите подходящий файл в зависимости от ваших потребностей:
|
|
||||||
|
|
||||||
| Файл | Для кого | Время чтения |
|
|
||||||
|------|----------|--------------|
|
|
||||||
| **[СЛЕДУЮЩИЕ_ШАГИ.md](СЛЕДУЮЩИЕ_ШАГИ.md)** | Все | 2 минуты |
|
|
||||||
| **[РЕЗЮМЕ.md](РЕЗЮМЕ.md)** | Все | 1 минута |
|
|
||||||
| **[ZITADEL_QUICK_START.md](ZITADEL_QUICK_START.md)** | Пользователи | 3 минуты |
|
|
||||||
| **[OPENID_CONNECT_SETUP.md](OPENID_CONNECT_SETUP.md)** | Администраторы | 10 минут |
|
|
||||||
| **[OIDC_CHANGES.md](OIDC_CHANGES.md)** | Разработчики | 5 минут |
|
|
||||||
| **[СХЕМА_РАБОТЫ.md](СХЕМА_РАБОТЫ.md)** | Разработчики | 5 минут |
|
|
||||||
| **[ИТОГИ_РАБОТЫ.md](ИТОГИ_РАБОТЫ.md)** | Менеджеры | 5 минут |
|
|
||||||
| **[ГОТОВО_OIDC.md](ГОТОВО_OIDC.md)** | Все | 3 минуты |
|
|
||||||
|
|
||||||
## ⚡ Быстрый старт
|
|
||||||
|
|
||||||
### 0. Установите зависимости (если ещё не установлены)
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
pip install authlib==1.3.0 httpx==0.26.0
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1. Создайте приложение в ZITADEL
|
|
||||||
```
|
|
||||||
→ zitadel.cloud
|
|
||||||
→ New Application
|
|
||||||
→ Web Application
|
|
||||||
→ Code (with PKCE)
|
|
||||||
→ Redirect URI: http://localhost:8000/api/auth/oidc/zitadel/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Настройте .env
|
|
||||||
```bash
|
|
||||||
# backend/.env
|
|
||||||
ZITADEL_ISSUER=https://your-instance.zitadel.cloud
|
|
||||||
ZITADEL_CLIENT_ID=123456789012345678@your-project
|
|
||||||
ZITADEL_CLIENT_SECRET=your-secret-here
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Запустите
|
|
||||||
```bash
|
|
||||||
# Backend
|
|
||||||
cd backend && python main.py
|
|
||||||
|
|
||||||
# Frontend
|
|
||||||
cd frontend && npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Проверьте
|
|
||||||
```
|
|
||||||
→ http://localhost:3000
|
|
||||||
→ Кнопка "Войти через ZITADEL" 🔐
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📖 Подробная документация
|
|
||||||
|
|
||||||
### Для пользователей
|
|
||||||
|
|
||||||
#### [СЛЕДУЮЩИЕ_ШАГИ.md](СЛЕДУЮЩИЕ_ШАГИ.md)
|
|
||||||
**Что делать сейчас**
|
|
||||||
- Краткий план действий
|
|
||||||
- Проверка работы
|
|
||||||
- Визуальное представление
|
|
||||||
- Решение проблем
|
|
||||||
|
|
||||||
#### [ZITADEL_QUICK_START.md](ZITADEL_QUICK_START.md)
|
|
||||||
**Быстрый старт за 4 шага**
|
|
||||||
- Создание приложения в ZITADEL
|
|
||||||
- Настройка .env файла
|
|
||||||
- Запуск приложения
|
|
||||||
- Проверка работы
|
|
||||||
|
|
||||||
#### [ГОТОВО_OIDC.md](ГОТОВО_OIDC.md)
|
|
||||||
**Итоговая инструкция**
|
|
||||||
- Что сделано
|
|
||||||
- Как использовать
|
|
||||||
- Документация
|
|
||||||
- Возможности
|
|
||||||
|
|
||||||
### Для администраторов
|
|
||||||
|
|
||||||
#### [OPENID_CONNECT_SETUP.md](OPENID_CONNECT_SETUP.md)
|
|
||||||
**Полное руководство**
|
|
||||||
- Подробная настройка ZITADEL
|
|
||||||
- Конфигурация backend
|
|
||||||
- Конфигурация frontend
|
|
||||||
- Безопасность
|
|
||||||
- Troubleshooting
|
|
||||||
|
|
||||||
#### [РЕЗЮМЕ.md](РЕЗЮМЕ.md)
|
|
||||||
**Краткое резюме**
|
|
||||||
- Что выполнено
|
|
||||||
- Список файлов
|
|
||||||
- Инструкция по использованию
|
|
||||||
- Таблица документации
|
|
||||||
|
|
||||||
### Для разработчиков
|
|
||||||
|
|
||||||
#### [OIDC_CHANGES.md](OIDC_CHANGES.md)
|
|
||||||
**Технические детали**
|
|
||||||
- Изменения в коде
|
|
||||||
- Структура данных
|
|
||||||
- API endpoints
|
|
||||||
- Зависимости
|
|
||||||
- Безопасность
|
|
||||||
|
|
||||||
#### [СХЕМА_РАБОТЫ.md](СХЕМА_РАБОТЫ.md)
|
|
||||||
**Визуальная схема**
|
|
||||||
- Поток аутентификации
|
|
||||||
- Диаграммы
|
|
||||||
- Структура данных
|
|
||||||
- Безопасность
|
|
||||||
|
|
||||||
#### [ИТОГИ_РАБОТЫ.md](ИТОГИ_РАБОТЫ.md)
|
|
||||||
**Полный отчёт**
|
|
||||||
- Что было сделано
|
|
||||||
- Проверка качества
|
|
||||||
- Статистика
|
|
||||||
- Результаты
|
|
||||||
|
|
||||||
## 🔍 Структура проекта
|
|
||||||
|
|
||||||
```
|
|
||||||
MC Panel/
|
|
||||||
├── backend/
|
|
||||||
│ ├── main.py # OAuth инициализация и endpoints
|
|
||||||
│ ├── oidc_config.py # Конфигурация ZITADEL
|
|
||||||
│ ├── .env.example # Пример настроек
|
|
||||||
│ └── requirements.txt # Зависимости (authlib, httpx)
|
|
||||||
│
|
|
||||||
├── frontend/
|
|
||||||
│ └── src/
|
|
||||||
│ ├── App.jsx # Обработка callback
|
|
||||||
│ └── components/
|
|
||||||
│ └── Auth.jsx # Кнопка ZITADEL
|
|
||||||
│
|
|
||||||
└── docs/
|
|
||||||
├── СЛЕДУЮЩИЕ_ШАГИ.md # Что делать сейчас
|
|
||||||
├── РЕЗЮМЕ.md # Краткое резюме
|
|
||||||
├── ZITADEL_QUICK_START.md # Быстрый старт
|
|
||||||
├── OPENID_CONNECT_SETUP.md # Полная инструкция
|
|
||||||
├── OIDC_CHANGES.md # Технические детали
|
|
||||||
├── СХЕМА_РАБОТЫ.md # Визуальная схема
|
|
||||||
├── ИТОГИ_РАБОТЫ.md # Полный отчёт
|
|
||||||
├── ГОТОВО_OIDC.md # Итоговая инструкция
|
|
||||||
└── README_OIDC.md # Этот файл
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✨ Возможности
|
|
||||||
|
|
||||||
### Функциональность
|
|
||||||
- ✅ Вход через ZITADEL
|
|
||||||
- ✅ Автоматическое создание пользователей
|
|
||||||
- ✅ Обновление данных при входе
|
|
||||||
- ✅ JWT токены для MC Panel
|
|
||||||
- ✅ Безопасность (PKCE, state, nonce)
|
|
||||||
|
|
||||||
### Интерфейс
|
|
||||||
- ✅ Кнопка "Войти через ZITADEL" 🔐
|
|
||||||
- ✅ Автоматический вход после OAuth
|
|
||||||
- ✅ Красивый дизайн
|
|
||||||
- ✅ Адаптивность
|
|
||||||
|
|
||||||
### Безопасность
|
|
||||||
- ✅ PKCE (Proof Key for Code Exchange)
|
|
||||||
- ✅ State parameter (CSRF защита)
|
|
||||||
- ✅ Nonce (replay защита)
|
|
||||||
- ✅ JWT с истечением
|
|
||||||
- ✅ Проверка redirect_uri
|
|
||||||
|
|
||||||
## 🎯 Рекомендации
|
|
||||||
|
|
||||||
### Для разработки
|
|
||||||
1. Используйте ZITADEL Cloud (бесплатно)
|
|
||||||
2. Тестируйте на localhost
|
|
||||||
3. Проверяйте логи backend
|
|
||||||
4. Используйте DevTools браузера
|
|
||||||
|
|
||||||
### Для продакшена
|
|
||||||
1. Используйте HTTPS
|
|
||||||
2. Настройте правильные redirect URIs
|
|
||||||
3. Регулярно обновляйте client_secret
|
|
||||||
4. Включите логирование
|
|
||||||
5. Добавьте мониторинг
|
|
||||||
|
|
||||||
## 🆘 Помощь
|
|
||||||
|
|
||||||
### Проблемы?
|
|
||||||
|
|
||||||
| Проблема | Решение |
|
|
||||||
|----------|---------|
|
|
||||||
| Кнопка не появляется | Проверьте `.env` и перезапустите backend |
|
|
||||||
| Ошибка redirect_uri | Проверьте настройки в ZITADEL |
|
|
||||||
| Ошибка invalid_client | Проверьте Client ID и Secret |
|
|
||||||
| Не создаётся пользователь | Проверьте логи backend |
|
|
||||||
|
|
||||||
**Подробнее:** [ZITADEL_QUICK_START.md](ZITADEL_QUICK_START.md) → Раздел "Проблемы?"
|
|
||||||
|
|
||||||
## 📊 Статистика
|
|
||||||
|
|
||||||
- **Файлов изменено:** 2
|
|
||||||
- **Файлов создано:** 8 (документация)
|
|
||||||
- **Файлов удалено:** 1
|
|
||||||
- **Строк кода:** ~50 изменено
|
|
||||||
- **Строк документации:** ~1500 добавлено
|
|
||||||
- **Время настройки:** ~7 минут
|
|
||||||
- **Сложность:** Низкая
|
|
||||||
|
|
||||||
## 🎉 Готово!
|
|
||||||
|
|
||||||
**OpenID Connect с ZITADEL полностью интегрирован и готов к использованию!**
|
|
||||||
|
|
||||||
### Следующий шаг
|
|
||||||
→ Откройте **[СЛЕДУЮЩИЕ_ШАГИ.md](СЛЕДУЮЩИЕ_ШАГИ.md)** и начните настройку!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Вопросы?** Смотрите документацию выше или проверьте логи backend.
|
|
||||||
|
|
||||||
**Всё работает?** Отлично! Можете начинать использовать систему! 🚀
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
# 🚀 MC Panel готова к использованию!
|
|
||||||
|
|
||||||
## Быстрый старт
|
|
||||||
|
|
||||||
### 1. Запустите бэкенд
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python main_new.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Запустите фронтенд
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Откройте панель
|
|
||||||
Откройте http://localhost:3000 в браузере
|
|
||||||
|
|
||||||
### 4. Войдите в систему
|
|
||||||
- **Логин**: admin
|
|
||||||
- **Пароль**: admin
|
|
||||||
|
|
||||||
## ✨ Возможности
|
|
||||||
|
|
||||||
### 🎨 Темы
|
|
||||||
- 5 тем на выбор: Тёмная, Светлая, Фиолетовая, Синяя, Зелёная
|
|
||||||
- Градиентный логотип "MC Panel" для каждой темы
|
|
||||||
- Автоматическое сохранение выбранной темы
|
|
||||||
|
|
||||||
### 🖥️ Управление серверами
|
|
||||||
- Создание и удаление серверов
|
|
||||||
- Запуск и остановка серверов
|
|
||||||
- Просмотр консоли в реальном времени
|
|
||||||
- Менеджер файлов с редактированием
|
|
||||||
- Мониторинг ресурсов (RAM, диск)
|
|
||||||
- Настройки сервера
|
|
||||||
|
|
||||||
### 👥 Пользователи
|
|
||||||
- Регистрация и авторизация
|
|
||||||
- Роли: Админ и Пользователь
|
|
||||||
- Управление доступом к серверам
|
|
||||||
- Владельцы серверов могут выдавать доступ другим пользователям
|
|
||||||
|
|
||||||
### 🌐 Сетевой доступ
|
|
||||||
- Работает через Radmin VPN
|
|
||||||
- Автоматическое определение API URL
|
|
||||||
- Поддержка локальной и сетевой работы
|
|
||||||
|
|
||||||
## 📱 Интерфейс
|
|
||||||
|
|
||||||
Современный дизайн в стиле TimeWeb Cloud:
|
|
||||||
- Карточки с тенями и анимациями
|
|
||||||
- Плавные переходы
|
|
||||||
- Адаптивный дизайн для мобильных
|
|
||||||
- Sticky header
|
|
||||||
- Анимированные индикаторы статуса
|
|
||||||
|
|
||||||
## 🎯 Готово!
|
|
||||||
|
|
||||||
Панель полностью настроена и готова к использованию. Наслаждайтесь! 🎉
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
ВАЖНО: Переименуйте файл backend/main_new.py в backend/main.py
|
|
||||||
|
|
||||||
Удалите старый backend/main.py (если есть) и переименуйте backend/main_new.py в backend/main.py
|
|
||||||
|
|
||||||
Это можно сделать вручную в проводнике Windows или командой:
|
|
||||||
cd backend
|
|
||||||
del main.py
|
|
||||||
ren main_new.py main.py
|
|
||||||
80
TEST_API.md
80
TEST_API.md
@@ -1,80 +0,0 @@
|
|||||||
# Тестирование API
|
|
||||||
|
|
||||||
## Проверка работы API
|
|
||||||
|
|
||||||
Откройте браузер и проверьте следующие URL (замените IP на ваш):
|
|
||||||
|
|
||||||
### 1. Проверка списка серверов
|
|
||||||
```
|
|
||||||
http://26.123.45.67:8000/api/servers
|
|
||||||
```
|
|
||||||
|
|
||||||
Должен вернуть JSON с массивом серверов.
|
|
||||||
|
|
||||||
### 2. Проверка конфигурации сервера
|
|
||||||
```
|
|
||||||
http://26.123.45.67:8000/api/servers/ИМЯ_СЕРВЕРА/config
|
|
||||||
```
|
|
||||||
|
|
||||||
Должен вернуть JSON с настройками сервера.
|
|
||||||
|
|
||||||
### 3. Проверка файлов сервера
|
|
||||||
```
|
|
||||||
http://26.123.45.67:8000/api/servers/ИМЯ_СЕРВЕРА/files
|
|
||||||
```
|
|
||||||
|
|
||||||
Должен вернуть JSON с массивом файлов.
|
|
||||||
|
|
||||||
## Проверка в консоли браузера
|
|
||||||
|
|
||||||
Откройте консоль браузера (F12) и выполните:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Проверка API URL
|
|
||||||
console.log('API URL:', window.location.protocol + '//' + window.location.hostname + ':8000');
|
|
||||||
|
|
||||||
// Проверка серверов
|
|
||||||
fetch('http://' + window.location.hostname + ':8000/api/servers')
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => console.log('Серверы:', data))
|
|
||||||
.catch(err => console.error('Ошибка:', err));
|
|
||||||
```
|
|
||||||
|
|
||||||
## Проверка логов бэкенда
|
|
||||||
|
|
||||||
В терминале где запущен бэкенд должны появляться сообщения:
|
|
||||||
- `Найдено серверов: X`
|
|
||||||
- `Загружена конфигурация для ...`
|
|
||||||
- `WebSocket подключен для сервера: ...`
|
|
||||||
|
|
||||||
Если сообщений нет, значит запросы не доходят до бэкенда.
|
|
||||||
|
|
||||||
## Возможные проблемы
|
|
||||||
|
|
||||||
### Проблема: Серверы показываются, но файлы/настройки не загружаются
|
|
||||||
|
|
||||||
**Причина:** Запросы идут на неправильный URL
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Откройте консоль браузера (F12)
|
|
||||||
2. Перейдите на вкладку Network
|
|
||||||
3. Попробуйте открыть файлы или настройки
|
|
||||||
4. Посмотрите на URL запросов - они должны начинаться с `http://ВАШ_IP:8000/api/`
|
|
||||||
|
|
||||||
### Проблема: CORS ошибки
|
|
||||||
|
|
||||||
**Причина:** Браузер блокирует запросы
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Перезапустите бэкенд
|
|
||||||
2. Убедитесь, что в логах бэкенда нет ошибок
|
|
||||||
3. Очистите кэш браузера
|
|
||||||
|
|
||||||
### Проблема: WebSocket не подключается
|
|
||||||
|
|
||||||
**Причина:** WebSocket использует неправильный протокол
|
|
||||||
|
|
||||||
**Решение:**
|
|
||||||
1. Проверьте файл `frontend/src/config.js`
|
|
||||||
2. WebSocket URL должен быть `ws://ВАШ_IP:8000`
|
|
||||||
3. Перезапустите фронтенд
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# ✅ Тема успешно применена!
|
|
||||||
|
|
||||||
## Что было сделано
|
|
||||||
|
|
||||||
### 🎨 Система тем
|
|
||||||
- ✅ Создано 5 тем: Тёмная, Светлая, Фиолетовая, Синяя, Зелёная
|
|
||||||
- ✅ Каждая тема имеет уникальный градиент для логотипа "MC Panel"
|
|
||||||
- ✅ Селектор тем добавлен в header
|
|
||||||
- ✅ Выбранная тема сохраняется в localStorage
|
|
||||||
|
|
||||||
### 🎯 Градиенты для "MC Panel"
|
|
||||||
- **Тёмная**: синий → фиолетовый (from-blue-400 to-purple-600)
|
|
||||||
- **Светлая**: синий → фиолетовый (from-blue-600 to-purple-600)
|
|
||||||
- **Фиолетовая**: фиолетовый → розовый (from-purple-400 to-pink-600)
|
|
||||||
- **Синяя**: голубой → синий (from-cyan-400 to-blue-600)
|
|
||||||
- **Зелёная**: изумрудный → зелёный (from-emerald-400 to-green-600)
|
|
||||||
|
|
||||||
### 🎨 Современный интерфейс
|
|
||||||
- ✅ Дизайн в стиле TimeWeb Cloud
|
|
||||||
- ✅ Карточки с тенями и анимациями
|
|
||||||
- ✅ Плавные переходы между темами
|
|
||||||
- ✅ Адаптивный дизайн для мобильных устройств
|
|
||||||
- ✅ Sticky header с информацией о пользователе
|
|
||||||
- ✅ Анимированные индикаторы статуса серверов
|
|
||||||
|
|
||||||
### 📱 Адаптивность
|
|
||||||
- ✅ Скрываемая боковая панель на мобильных
|
|
||||||
- ✅ Адаптивные кнопки (текст скрывается на маленьких экранах)
|
|
||||||
- ✅ Горизонтальная прокрутка вкладок
|
|
||||||
|
|
||||||
## Как использовать
|
|
||||||
|
|
||||||
### Смена темы
|
|
||||||
1. Нажмите на селектор тем в правом верхнем углу
|
|
||||||
2. Выберите нужную тему из списка
|
|
||||||
3. Тема применится мгновенно и сохранится автоматически
|
|
||||||
|
|
||||||
### Файлы с темами
|
|
||||||
- `frontend/src/themes.js` - конфигурация всех тем
|
|
||||||
- `frontend/src/App.jsx` - главный компонент с темами
|
|
||||||
- `frontend/src/components/Auth.jsx` - страница входа с темами
|
|
||||||
- `frontend/src/components/ThemeSelector.jsx` - селектор тем
|
|
||||||
|
|
||||||
## Готово! 🎉
|
|
||||||
|
|
||||||
Панель теперь имеет современный интерфейс с 5 темами и градиентным логотипом "MC Panel".
|
|
||||||
Все компоненты автоматически используют цвета выбранной темы.
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
# ✅ Система тем полностью готова!
|
|
||||||
|
|
||||||
## Что было исправлено и улучшено
|
|
||||||
|
|
||||||
### 🎨 Градиентный логотип "MC Panel"
|
|
||||||
- ✅ Добавлен градиент в Auth.jsx (страница входа)
|
|
||||||
- ✅ Добавлен градиент в App.jsx (главная панель)
|
|
||||||
- ✅ Каждая тема имеет свой уникальный градиент:
|
|
||||||
- **Тёмная**: синий → фиолетовый
|
|
||||||
- **Светлая**: синий → фиолетовый
|
|
||||||
- **Фиолетовая**: фиолетовый → розовый
|
|
||||||
- **Синяя**: голубой → синий
|
|
||||||
- **Зелёная**: изумрудный → зелёный
|
|
||||||
|
|
||||||
### 🎯 Обновлённые компоненты
|
|
||||||
1. **ThemeSelector.jsx** - теперь использует динамические цвета из текущей темы
|
|
||||||
2. **CreateServerModal.jsx** - обновлён для использования тем с современным дизайном
|
|
||||||
3. **App.jsx** - добавлен градиент для логотипа
|
|
||||||
4. **Auth.jsx** - добавлен градиент для логотипа
|
|
||||||
|
|
||||||
### 📁 Структура файлов
|
|
||||||
```
|
|
||||||
frontend/src/
|
|
||||||
├── themes.js # Конфигурация всех тем
|
|
||||||
├── App.jsx # Главный компонент с темами ✅
|
|
||||||
├── components/
|
|
||||||
│ ├── Auth.jsx # Страница входа с темами ✅
|
|
||||||
│ ├── ThemeSelector.jsx # Селектор тем ✅
|
|
||||||
│ ├── CreateServerModal.jsx # Модальное окно создания сервера ✅
|
|
||||||
│ ├── Console.jsx # Получает theme prop
|
|
||||||
│ ├── FileManager.jsx # Получает theme prop
|
|
||||||
│ ├── Stats.jsx # Получает theme prop
|
|
||||||
│ ├── ServerSettings.jsx # Получает theme prop
|
|
||||||
│ └── Users.jsx # Получает theme prop
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Как использовать
|
|
||||||
|
|
||||||
### Запуск панели
|
|
||||||
```bash
|
|
||||||
# Терминал 1 - Бэкенд
|
|
||||||
cd backend
|
|
||||||
python main_new.py
|
|
||||||
|
|
||||||
# Терминал 2 - Фронтенд
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Смена темы
|
|
||||||
1. Откройте панель в браузере
|
|
||||||
2. Нажмите на иконку палитры (🎨) в правом верхнем углу
|
|
||||||
3. Выберите нужную тему из выпадающего меню
|
|
||||||
4. Тема применится мгновенно и сохранится автоматически
|
|
||||||
|
|
||||||
### Доступные темы
|
|
||||||
- 🌑 **Тёмная** - классическая тёмная тема (по умолчанию)
|
|
||||||
- ☀️ **Светлая** - светлая тема для дневного использования
|
|
||||||
- 💜 **Фиолетовая** - стильная фиолетовая палитра
|
|
||||||
- 💙 **Синяя** - холодная синяя тема
|
|
||||||
- 💚 **Зелёная** - природная зелёная тема
|
|
||||||
|
|
||||||
## ✨ Особенности
|
|
||||||
|
|
||||||
### Градиентный логотип
|
|
||||||
Логотип "MC Panel" теперь использует градиент, который меняется в зависимости от выбранной темы:
|
|
||||||
```jsx
|
|
||||||
<h1 className={`text-xl font-bold bg-gradient-to-r ${currentTheme.gradient} bg-clip-text text-transparent`}>
|
|
||||||
MC Panel
|
|
||||||
</h1>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Автоматическое сохранение
|
|
||||||
Выбранная тема автоматически сохраняется в `localStorage` и применяется при следующем входе.
|
|
||||||
|
|
||||||
### Плавные переходы
|
|
||||||
Все элементы интерфейса имеют плавные переходы при смене темы благодаря классу `transition-colors duration-300`.
|
|
||||||
|
|
||||||
### Адаптивный дизайн
|
|
||||||
- Скрываемая боковая панель на мобильных устройствах
|
|
||||||
- Адаптивные кнопки (текст скрывается на маленьких экранах)
|
|
||||||
- Горизонтальная прокрутка вкладок на мобильных
|
|
||||||
|
|
||||||
## 🎉 Готово!
|
|
||||||
|
|
||||||
Панель MC Panel теперь имеет полноценную систему тем с градиентным логотипом и современным интерфейсом в стиле TimeWeb Cloud. Все компоненты используют цвета из выбранной темы, обеспечивая единообразный и красивый дизайн.
|
|
||||||
|
|
||||||
Наслаждайтесь использованием! 🚀
|
|
||||||
125
THEME_UPDATE.md
125
THEME_UPDATE.md
@@ -1,125 +0,0 @@
|
|||||||
# Обновление: Система тем и современный интерфейс
|
|
||||||
|
|
||||||
## Что добавлено
|
|
||||||
|
|
||||||
### 🎨 Система тем
|
|
||||||
- **5 тем на выбор:**
|
|
||||||
- Тёмная (по умолчанию)
|
|
||||||
- Светлая
|
|
||||||
- Фиолетовая
|
|
||||||
- Синяя
|
|
||||||
- Зелёная
|
|
||||||
|
|
||||||
### 🎯 Современный интерфейс в стиле TimeWeb Cloud
|
|
||||||
- Чистый и минималистичный дизайн
|
|
||||||
- Плавные переходы и анимации
|
|
||||||
- Адаптивная вёрстка
|
|
||||||
- Улучшенная типографика
|
|
||||||
- Современные карточки и кнопки
|
|
||||||
- Sticky header
|
|
||||||
- Анимированные индикаторы статуса
|
|
||||||
|
|
||||||
## Установка
|
|
||||||
|
|
||||||
### 1. Замените App.jsx
|
|
||||||
|
|
||||||
Переименуйте файлы:
|
|
||||||
```
|
|
||||||
frontend/src/App.jsx → frontend/src/App_old.jsx (бэкап)
|
|
||||||
frontend/src/App_modern.jsx → frontend/src/App.jsx
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Перезапустите фронтенд
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
### Смена темы
|
|
||||||
|
|
||||||
1. Нажмите на иконку палитры (🎨) в правом верхнем углу
|
|
||||||
2. Выберите нужную тему из выпадающего меню
|
|
||||||
3. Тема сохраняется автоматически
|
|
||||||
|
|
||||||
### Особенности интерфейса
|
|
||||||
|
|
||||||
**Header:**
|
|
||||||
- Sticky (прилипает к верху при прокрутке)
|
|
||||||
- Показывает статус подключения
|
|
||||||
- Отображает текущего пользователя и роль
|
|
||||||
- Кнопки быстрого доступа
|
|
||||||
|
|
||||||
**Sidebar:**
|
|
||||||
- Список серверов с карточками
|
|
||||||
- Кнопки запуска/остановки на каждой карточке
|
|
||||||
- Анимированный индикатор статуса (пульсирует когда запущен)
|
|
||||||
- Скрывается на мобильных устройствах
|
|
||||||
|
|
||||||
**Вкладки:**
|
|
||||||
- Современный дизайн с подчёркиванием
|
|
||||||
- Иконки для каждой вкладки
|
|
||||||
- Плавные переходы
|
|
||||||
|
|
||||||
**Карточки серверов:**
|
|
||||||
- Закруглённые углы
|
|
||||||
- Тени при наведении
|
|
||||||
- Цветовая индикация выбранного сервера
|
|
||||||
- Плавные анимации
|
|
||||||
|
|
||||||
## Темы
|
|
||||||
|
|
||||||
### Тёмная (Dark)
|
|
||||||
- Основной: Серый 900
|
|
||||||
- Вторичный: Серый 800
|
|
||||||
- Акцент: Синий 600
|
|
||||||
|
|
||||||
### Светлая (Light)
|
|
||||||
- Основной: Серый 50
|
|
||||||
- Вторичный: Белый
|
|
||||||
- Акцент: Синий 600
|
|
||||||
|
|
||||||
### Фиолетовая (Purple)
|
|
||||||
- Основной: Фиолетовый 950
|
|
||||||
- Вторичный: Фиолетовый 900
|
|
||||||
- Акцент: Фиолетовый 600
|
|
||||||
|
|
||||||
### Синяя (Blue)
|
|
||||||
- Основной: Синий 950
|
|
||||||
- Вторичный: Синий 900
|
|
||||||
- Акцент: Синий 500
|
|
||||||
|
|
||||||
### Зелёная (Green)
|
|
||||||
- Основной: Зелёный 950
|
|
||||||
- Вторичный: Зелёный 900
|
|
||||||
- Акцент: Зелёный 600
|
|
||||||
|
|
||||||
## Файлы
|
|
||||||
|
|
||||||
- `frontend/src/App_modern.jsx` - новый App с темами
|
|
||||||
- `frontend/src/themes.js` - конфигурация тем
|
|
||||||
- `frontend/src/components/ThemeSelector.jsx` - селектор тем
|
|
||||||
|
|
||||||
## Что дальше
|
|
||||||
|
|
||||||
Компоненты (Console, FileManager, Stats, ServerSettings) нужно обновить для поддержки тем.
|
|
||||||
Они получают `theme` prop с текущей темой.
|
|
||||||
|
|
||||||
Пример использования в компоненте:
|
|
||||||
```jsx
|
|
||||||
export default function MyComponent({ theme }) {
|
|
||||||
return (
|
|
||||||
<div className={`${theme.primary} ${theme.text}`}>
|
|
||||||
<button className={`${theme.accent} ${theme.accentHover}`}>
|
|
||||||
Кнопка
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Готово! 🎉
|
|
||||||
|
|
||||||
Теперь у вас современный интерфейс с системой тем!
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
# 🎫 Система тикетов
|
|
||||||
|
|
||||||
## Что добавлено
|
|
||||||
|
|
||||||
### ✅ Новые возможности
|
|
||||||
|
|
||||||
1. **Система тикетов** - полноценная система поддержки с чатом
|
|
||||||
2. **Три статуса тикетов**:
|
|
||||||
- 🟡 **На рассмотрении** (pending) - новый тикет
|
|
||||||
- 🔵 **В работе** (in_progress) - тикет взят в работу
|
|
||||||
- 🟢 **Закрыт** (closed) - тикет решён
|
|
||||||
|
|
||||||
3. **Новая роль "Тех. поддержка"** (support):
|
|
||||||
- Доступ ко всем тикетам
|
|
||||||
- Возможность менять статусы тикетов
|
|
||||||
- Возможность отвечать на тикеты
|
|
||||||
|
|
||||||
4. **Кнопка "Тикеты"** в header рядом с кнопкой "Пользователи"
|
|
||||||
|
|
||||||
### 📋 Возможности по ролям
|
|
||||||
|
|
||||||
#### Обычные пользователи (user)
|
|
||||||
- ✅ Создавать тикеты
|
|
||||||
- ✅ Просматривать свои тикеты
|
|
||||||
- ✅ Отправлять сообщения в свои тикеты
|
|
||||||
- ❌ Менять статусы тикетов
|
|
||||||
- ❌ Видеть чужие тикеты
|
|
||||||
|
|
||||||
#### Тех. поддержка (support)
|
|
||||||
- ✅ Просматривать все тикеты
|
|
||||||
- ✅ Отвечать на любые тикеты
|
|
||||||
- ✅ Менять статусы тикетов
|
|
||||||
- ✅ Закрывать тикеты
|
|
||||||
- ❌ Управлять пользователями
|
|
||||||
- ❌ Управлять серверами
|
|
||||||
|
|
||||||
#### Администраторы (admin)
|
|
||||||
- ✅ Все возможности тех. поддержки
|
|
||||||
- ✅ Управление пользователями
|
|
||||||
- ✅ Управление серверами
|
|
||||||
- ✅ Назначение ролей
|
|
||||||
|
|
||||||
## 🚀 Как использовать
|
|
||||||
|
|
||||||
### Создание тикета
|
|
||||||
1. Нажмите кнопку "Тикеты" в header
|
|
||||||
2. Нажмите "Создать тикет"
|
|
||||||
3. Заполните тему и описание проблемы
|
|
||||||
4. Нажмите "Создать"
|
|
||||||
|
|
||||||
### Работа с тикетом
|
|
||||||
1. Откройте список тикетов
|
|
||||||
2. Нажмите на нужный тикет
|
|
||||||
3. Пишите сообщения в чат
|
|
||||||
4. Тех. поддержка и админы могут менять статус тикета
|
|
||||||
|
|
||||||
### Назначение роли "Тех. поддержка"
|
|
||||||
1. Войдите как администратор (none / none)
|
|
||||||
2. Нажмите кнопку "Пользователи"
|
|
||||||
3. Найдите нужного пользователя
|
|
||||||
4. В выпадающем списке выберите "Тех. поддержка"
|
|
||||||
5. Роль изменится автоматически
|
|
||||||
|
|
||||||
## 📁 Новые файлы
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- `backend/tickets.json` - хранилище тикетов (создаётся автоматически)
|
|
||||||
- Добавлены endpoints в `backend/main.py`:
|
|
||||||
- `GET /api/tickets` - список тикетов
|
|
||||||
- `POST /api/tickets/create` - создать тикет
|
|
||||||
- `GET /api/tickets/{id}` - получить тикет
|
|
||||||
- `POST /api/tickets/{id}/message` - добавить сообщение
|
|
||||||
- `PUT /api/tickets/{id}/status` - изменить статус
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- `frontend/src/components/Tickets.jsx` - список тикетов
|
|
||||||
- `frontend/src/components/TicketChat.jsx` - чат тикета
|
|
||||||
- `frontend/src/components/CreateTicketModal.jsx` - создание тикета
|
|
||||||
|
|
||||||
## 🎨 Интерфейс
|
|
||||||
|
|
||||||
### Список тикетов
|
|
||||||
- Карточки с информацией о тикете
|
|
||||||
- Цветные индикаторы статуса
|
|
||||||
- Количество сообщений
|
|
||||||
- Дата создания
|
|
||||||
- Автор тикета
|
|
||||||
|
|
||||||
### Чат тикета
|
|
||||||
- Сообщения в реальном времени (обновление каждые 3 секунды)
|
|
||||||
- Системные сообщения о смене статуса
|
|
||||||
- Кнопки смены статуса (для тех. поддержки и админов)
|
|
||||||
- Отправка сообщений (если тикет не закрыт)
|
|
||||||
|
|
||||||
## 🔧 Технические детали
|
|
||||||
|
|
||||||
### Статусы тикетов
|
|
||||||
```javascript
|
|
||||||
pending // На рассмотрении (жёлтый)
|
|
||||||
in_progress // В работе (синий)
|
|
||||||
closed // Закрыт (зелёный)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Роли пользователей
|
|
||||||
```javascript
|
|
||||||
user // Обычный пользователь
|
|
||||||
support // Тех. поддержка
|
|
||||||
admin // Администратор
|
|
||||||
```
|
|
||||||
|
|
||||||
### Структура тикета
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "1",
|
|
||||||
"title": "Проблема с сервером",
|
|
||||||
"description": "Описание проблемы",
|
|
||||||
"author": "username",
|
|
||||||
"status": "pending",
|
|
||||||
"created_at": "2024-01-14T12:00:00",
|
|
||||||
"updated_at": "2024-01-14T12:00:00",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"author": "username",
|
|
||||||
"text": "Текст сообщения",
|
|
||||||
"timestamp": "2024-01-14T12:00:00"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✅ Готово!
|
|
||||||
|
|
||||||
Система тикетов полностью интегрирована в MC Panel. Пользователи могут создавать тикеты, а тех. поддержка и администраторы могут на них отвечать и управлять статусами.
|
|
||||||
|
|
||||||
### Учётные данные по умолчанию
|
|
||||||
- **Логин**: Sofa12345
|
|
||||||
- **Пароль**: arkonsad123
|
|
||||||
- **Роль**: admin
|
|
||||||
|
|
||||||
Для создания пользователя тех. поддержки:
|
|
||||||
1. Зарегистрируйте нового пользователя
|
|
||||||
2. Войдите как админ
|
|
||||||
3. Назначьте ему роль "Тех. поддержка"
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
# 👁️ Просмотр профилей пользователей
|
|
||||||
|
|
||||||
## Что добавлено
|
|
||||||
|
|
||||||
### Возможность просмотра профилей для админов и тех. поддержки
|
|
||||||
Администраторы и сотрудники технической поддержки теперь могут просматривать личные кабинеты других пользователей, нажав на их логин в списке пользователей.
|
|
||||||
|
|
||||||
## 🎯 Как использовать
|
|
||||||
|
|
||||||
### Просмотр профиля пользователя
|
|
||||||
1. Войдите как администратор или тех. поддержка
|
|
||||||
2. Нажмите кнопку "Пользователи" в header
|
|
||||||
3. Найдите нужного пользователя в списке
|
|
||||||
4. **Нажмите на логин пользователя** (он теперь кликабельный и подсвечивается при наведении)
|
|
||||||
5. Откроется личный кабинет этого пользователя
|
|
||||||
|
|
||||||
### Что можно увидеть
|
|
||||||
- ✅ Имя пользователя
|
|
||||||
- ✅ Роль пользователя
|
|
||||||
- ✅ Статистику по серверам (всего, мои, доступные)
|
|
||||||
- ✅ Список серверов пользователя
|
|
||||||
- ✅ Статистику по тикетам (всего, по статусам)
|
|
||||||
|
|
||||||
### Что нельзя сделать
|
|
||||||
- ❌ Изменить имя пользователя (вкладка скрыта)
|
|
||||||
- ❌ Изменить пароль пользователя (вкладка скрыта)
|
|
||||||
- ❌ Редактировать профиль другого пользователя
|
|
||||||
|
|
||||||
## 🎨 Визуальные изменения
|
|
||||||
|
|
||||||
### В списке пользователей (Users.jsx)
|
|
||||||
- **Логин пользователя** теперь кликабельный
|
|
||||||
- При наведении логин подсвечивается синим цветом
|
|
||||||
- Курсор меняется на pointer (указатель)
|
|
||||||
- Подсказка "Просмотреть профиль" при наведении
|
|
||||||
|
|
||||||
### В личном кабинете (Profile.jsx)
|
|
||||||
- **Заголовок**: "Профиль пользователя: [username]" (вместо "Личный кабинет")
|
|
||||||
- **Подзаголовок**: "Просмотр профиля другого пользователя"
|
|
||||||
- **Вкладки**: скрыты вкладки "Имя пользователя" и "Пароль"
|
|
||||||
- **Только вкладка "Обзор"**: показывается статистика пользователя
|
|
||||||
|
|
||||||
## 📋 Технические детали
|
|
||||||
|
|
||||||
### Backend (main.py)
|
|
||||||
|
|
||||||
#### Новый endpoint
|
|
||||||
```python
|
|
||||||
@app.get("/api/profile/stats/{username}")
|
|
||||||
async def get_user_profile_stats(username: str, user: dict = Depends(get_current_user)):
|
|
||||||
"""Получить статистику профиля другого пользователя"""
|
|
||||||
# Проверка прав доступа
|
|
||||||
if user["role"] not in ["admin", "support"]:
|
|
||||||
raise HTTPException(403, "Недостаточно прав")
|
|
||||||
|
|
||||||
# Возвращает статистику указанного пользователя
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Проверка прав
|
|
||||||
- Только администраторы и тех. поддержка могут просматривать чужие профили
|
|
||||||
- Обычные пользователи получат ошибку 403
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
#### App.jsx
|
|
||||||
```javascript
|
|
||||||
const [viewingUsername, setViewingUsername] = useState(null);
|
|
||||||
|
|
||||||
const handleViewProfile = (username) => {
|
|
||||||
setViewingUsername(username);
|
|
||||||
setShowProfile(true);
|
|
||||||
setShowUsers(false);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Users.jsx
|
|
||||||
```javascript
|
|
||||||
<button
|
|
||||||
onClick={() => onViewProfile && onViewProfile(user.username)}
|
|
||||||
className="text-lg font-semibold hover:text-blue-400 transition cursor-pointer"
|
|
||||||
title="Просмотреть профиль"
|
|
||||||
>
|
|
||||||
{user.username}
|
|
||||||
</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Profile.jsx
|
|
||||||
```javascript
|
|
||||||
const isViewingOther = viewingUsername && viewingUsername !== user?.username;
|
|
||||||
|
|
||||||
const loadStats = async () => {
|
|
||||||
const endpoint = isViewingOther
|
|
||||||
? `${API_URL}/api/profile/stats/${viewingUsername}`
|
|
||||||
: `${API_URL}/api/profile/stats`;
|
|
||||||
// ...
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔐 Безопасность
|
|
||||||
|
|
||||||
### Проверка прав на уровне API
|
|
||||||
- Endpoint `/api/profile/stats/{username}` проверяет роль пользователя
|
|
||||||
- Только `admin` и `support` могут получить доступ
|
|
||||||
- Обычные пользователи получат ошибку 403
|
|
||||||
|
|
||||||
### Защита на уровне UI
|
|
||||||
- Вкладки изменения имени и пароля скрыты при просмотре чужого профиля
|
|
||||||
- Невозможно редактировать данные другого пользователя
|
|
||||||
- Только просмотр статистики
|
|
||||||
|
|
||||||
## 📊 Доступные роли
|
|
||||||
|
|
||||||
### Кто может просматривать чужие профили
|
|
||||||
1. **Администратор** (admin) - ✅ Может просматривать все профили
|
|
||||||
2. **Тех. поддержка** (support) - ✅ Может просматривать все профили
|
|
||||||
3. **Пользователь** (user) - ❌ Не может просматривать чужие профили
|
|
||||||
4. **Забанен** (banned) - ❌ Не имеет доступа к панели
|
|
||||||
|
|
||||||
## ✅ Примеры использования
|
|
||||||
|
|
||||||
### Сценарий 1: Проверка активности пользователя
|
|
||||||
1. Админ хочет проверить, сколько серверов у пользователя
|
|
||||||
2. Открывает "Пользователи"
|
|
||||||
3. Нажимает на логин пользователя
|
|
||||||
4. Видит статистику: 3 сервера, 5 тикетов
|
|
||||||
|
|
||||||
### Сценарий 2: Помощь пользователю
|
|
||||||
1. Тех. поддержка получила тикет от пользователя
|
|
||||||
2. Хочет посмотреть его серверы для диагностики
|
|
||||||
3. Открывает "Пользователи"
|
|
||||||
4. Нажимает на логин пользователя
|
|
||||||
5. Видит список серверов и их названия
|
|
||||||
|
|
||||||
### Сценарий 3: Модерация
|
|
||||||
1. Админ хочет проверить активность пользователя перед баном
|
|
||||||
2. Открывает профиль пользователя
|
|
||||||
3. Видит статистику по тикетам и серверам
|
|
||||||
4. Принимает решение о блокировке
|
|
||||||
|
|
||||||
## 🎯 Возврат к списку пользователей
|
|
||||||
|
|
||||||
### Из профиля пользователя
|
|
||||||
1. Нажмите кнопку "Серверы" в header
|
|
||||||
2. Вы вернётесь к главной странице
|
|
||||||
3. Снова откройте "Пользователи" для просмотра других профилей
|
|
||||||
|
|
||||||
### Или откройте свой профиль
|
|
||||||
1. Нажмите кнопку "Личный кабинет" в header
|
|
||||||
2. Откроется ваш собственный профиль
|
|
||||||
3. Будут доступны все вкладки (Обзор, Имя пользователя, Пароль)
|
|
||||||
|
|
||||||
## ⚠️ Важные замечания
|
|
||||||
|
|
||||||
### Ограничения
|
|
||||||
- Нельзя редактировать чужие профили
|
|
||||||
- Нельзя изменить имя или пароль другого пользователя
|
|
||||||
- Только просмотр статистики
|
|
||||||
|
|
||||||
### Рекомендации
|
|
||||||
- Используйте эту функцию для помощи пользователям
|
|
||||||
- Не злоупотребляйте просмотром чужих профилей
|
|
||||||
- Соблюдайте конфиденциальность данных пользователей
|
|
||||||
|
|
||||||
## ✅ Готово!
|
|
||||||
|
|
||||||
Функция просмотра профилей пользователей полностью интегрирована в MC Panel. Администраторы и тех. поддержка могут легко просматривать информацию о пользователях для помощи и модерации.
|
|
||||||
|
|
||||||
### Тестирование
|
|
||||||
|
|
||||||
1. **Войдите как администратор**
|
|
||||||
- Логин: none
|
|
||||||
- Пароль: none
|
|
||||||
|
|
||||||
2. **Создайте тестового пользователя**
|
|
||||||
- Зарегистрируйте нового пользователя
|
|
||||||
- Создайте несколько серверов от его имени
|
|
||||||
|
|
||||||
3. **Просмотрите его профиль**
|
|
||||||
- Откройте "Пользователи"
|
|
||||||
- Нажмите на логин тестового пользователя
|
|
||||||
- Увидите его статистику
|
|
||||||
|
|
||||||
4. **Вернитесь к своему профилю**
|
|
||||||
- Нажмите "Личный кабинет"
|
|
||||||
- Откроется ваш профиль со всеми вкладками
|
|
||||||
|
|
||||||
**Удобного использования! 👁️**
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# 🚀 Быстрый старт с ZITADEL
|
|
||||||
|
|
||||||
## Что нужно сделать
|
|
||||||
|
|
||||||
### 1️⃣ Создать приложение в ZITADEL
|
|
||||||
|
|
||||||
1. Зайдите на [zitadel.cloud](https://zitadel.cloud) или используйте свой инстанс
|
|
||||||
2. Создайте новый проект или выберите существующий
|
|
||||||
3. Нажмите **"New Application"**
|
|
||||||
4. Выберите **"Web Application"**
|
|
||||||
5. Выберите **"Code (with PKCE)"**
|
|
||||||
6. Добавьте Redirect URI: `http://localhost:8000/api/auth/oidc/zitadel/callback`
|
|
||||||
7. Сохраните **Client ID** и **Client Secret**
|
|
||||||
|
|
||||||
### 2️⃣ Настроить .env файл
|
|
||||||
|
|
||||||
Откройте `backend/.env` и добавьте:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# ZITADEL Configuration
|
|
||||||
ZITADEL_ISSUER=https://your-instance.zitadel.cloud
|
|
||||||
ZITADEL_CLIENT_ID=123456789012345678@your-project
|
|
||||||
ZITADEL_CLIENT_SECRET=your-secret-key-here
|
|
||||||
|
|
||||||
# URLs
|
|
||||||
BASE_URL=http://localhost:8000
|
|
||||||
FRONTEND_URL=http://localhost:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3️⃣ Запустить приложение
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backend
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
|
|
||||||
# Frontend (в другом терминале)
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4️⃣ Проверить
|
|
||||||
|
|
||||||
1. Откройте http://localhost:3000
|
|
||||||
2. Увидите кнопку **"Войти через ZITADEL"** 🔐
|
|
||||||
3. Нажмите и войдите через ZITADEL
|
|
||||||
4. Готово! ✅
|
|
||||||
|
|
||||||
## Что происходит?
|
|
||||||
|
|
||||||
1. **Пользователь нажимает кнопку** → Перенаправление на ZITADEL
|
|
||||||
2. **Вход в ZITADEL** → Пользователь вводит логин/пароль
|
|
||||||
3. **Callback** → ZITADEL возвращает код авторизации
|
|
||||||
4. **Обмен кода на токен** → Backend получает данные пользователя
|
|
||||||
5. **Создание пользователя** → Автоматическое создание в системе
|
|
||||||
6. **JWT токен** → Пользователь получает токен для доступа
|
|
||||||
7. **Автоматический вход** → Перенаправление в панель
|
|
||||||
|
|
||||||
## Проблемы?
|
|
||||||
|
|
||||||
### Кнопка ZITADEL не появляется
|
|
||||||
- Проверьте `.env` файл
|
|
||||||
- Убедитесь что `ZITADEL_CLIENT_ID` и `ZITADEL_ISSUER` заполнены
|
|
||||||
- Перезапустите backend
|
|
||||||
|
|
||||||
### Ошибка "Invalid redirect_uri"
|
|
||||||
- Проверьте Redirect URI в настройках ZITADEL
|
|
||||||
- Должен быть: `http://localhost:8000/api/auth/oidc/zitadel/callback`
|
|
||||||
|
|
||||||
### Ошибка "Invalid client"
|
|
||||||
- Проверьте `ZITADEL_CLIENT_ID` и `ZITADEL_CLIENT_SECRET`
|
|
||||||
- Убедитесь что приложение активно в ZITADEL
|
|
||||||
|
|
||||||
## Готово! 🎉
|
|
||||||
|
|
||||||
Теперь пользователи могут входить через ZITADEL!
|
|
||||||
145
backend/main.py
145
backend/main.py
@@ -73,8 +73,8 @@ IS_WINDOWS = sys.platform == 'win32'
|
|||||||
def init_users():
|
def init_users():
|
||||||
if not USERS_FILE.exists():
|
if not USERS_FILE.exists():
|
||||||
admin_user = {
|
admin_user = {
|
||||||
"username": "Sofa12345",
|
"username": "Root",
|
||||||
"password": pwd_context.hash("arkonsad123"),
|
"password": pwd_context.hash("Admin"),
|
||||||
"role": "admin",
|
"role": "admin",
|
||||||
"servers": []
|
"servers": []
|
||||||
}
|
}
|
||||||
@@ -1005,23 +1005,88 @@ async def download_file(server_name: str, path: str, user: dict = Depends(get_cu
|
|||||||
|
|
||||||
@app.post("/api/servers/{server_name}/files/upload")
|
@app.post("/api/servers/{server_name}/files/upload")
|
||||||
async def upload_file(server_name: str, path: str, file: UploadFile = File(...), user: dict = Depends(get_current_user)):
|
async def upload_file(server_name: str, path: str, file: UploadFile = File(...), user: dict = Depends(get_current_user)):
|
||||||
|
print(f"Upload request: server={server_name}, path='{path}', filename='{file.filename}'")
|
||||||
|
|
||||||
if not check_server_access(user, server_name):
|
if not check_server_access(user, server_name):
|
||||||
raise HTTPException(403, "Нет доступа к этому серверу")
|
raise HTTPException(403, "Нет доступа к этому серверу")
|
||||||
|
|
||||||
server_path = SERVERS_DIR / server_name
|
server_path = SERVERS_DIR / server_name
|
||||||
target_path = server_path / path / file.filename
|
target_path = server_path / path / file.filename
|
||||||
|
|
||||||
|
print(f"Target path: {target_path}")
|
||||||
|
print(f"Server path: {server_path}")
|
||||||
|
print(f"Path starts with server_path: {str(target_path).startswith(str(server_path))}")
|
||||||
|
|
||||||
if not str(target_path).startswith(str(server_path)):
|
if not str(target_path).startswith(str(server_path)):
|
||||||
raise HTTPException(400, "Недопустимый путь")
|
raise HTTPException(400, "Недопустимый путь")
|
||||||
|
|
||||||
|
try:
|
||||||
target_path.parent.mkdir(parents=True, exist_ok=True)
|
target_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
print(f"Created directory: {target_path.parent}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating directory: {e}")
|
||||||
|
raise HTTPException(500, f"Ошибка создания директории: {str(e)}")
|
||||||
|
|
||||||
|
try:
|
||||||
with open(target_path, "wb") as f:
|
with open(target_path, "wb") as f:
|
||||||
content = await file.read()
|
content = await file.read()
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
print(f"File written successfully: {target_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error writing file: {e}")
|
||||||
|
raise HTTPException(500, f"Ошибка записи файла: {str(e)}")
|
||||||
|
|
||||||
return {"message": "Файл загружен"}
|
return {"message": "Файл загружен"}
|
||||||
|
|
||||||
|
@app.post("/api/servers/{server_name}/files/create")
|
||||||
|
async def create_file_or_folder(server_name: str, data: dict, user: dict = Depends(get_current_user)):
|
||||||
|
"""Создать новый файл или папку"""
|
||||||
|
if not check_server_access(user, server_name):
|
||||||
|
raise HTTPException(403, "Нет доступа к этому серверу")
|
||||||
|
|
||||||
|
item_type = data.get("type") # "file" or "folder"
|
||||||
|
name = data.get("name", "").strip()
|
||||||
|
path = data.get("path", "") # Текущая папка
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
raise HTTPException(400, "Имя не может быть пустым")
|
||||||
|
|
||||||
|
if item_type not in ["file", "folder"]:
|
||||||
|
raise HTTPException(400, "Тип должен быть 'file' или 'folder'")
|
||||||
|
|
||||||
|
server_path = SERVERS_DIR / server_name
|
||||||
|
|
||||||
|
# Формируем полный путь
|
||||||
|
if path:
|
||||||
|
full_path = server_path / path / name
|
||||||
|
else:
|
||||||
|
full_path = server_path / name
|
||||||
|
|
||||||
|
print(f"Creating {item_type}: {full_path}")
|
||||||
|
|
||||||
|
# Проверка безопасности
|
||||||
|
if not str(full_path).startswith(str(server_path)):
|
||||||
|
raise HTTPException(400, "Недопустимый путь")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if item_type == "folder":
|
||||||
|
# Создаем папку
|
||||||
|
full_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
# Создаем .gitkeep чтобы папка не была пустой
|
||||||
|
gitkeep = full_path / ".gitkeep"
|
||||||
|
gitkeep.touch()
|
||||||
|
print(f"Folder created: {full_path}")
|
||||||
|
else:
|
||||||
|
# Создаем файл
|
||||||
|
full_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
full_path.touch()
|
||||||
|
print(f"File created: {full_path}")
|
||||||
|
|
||||||
|
return {"message": f"{'Папка' if item_type == 'folder' else 'Файл'} создан(а)", "path": str(full_path)}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating {item_type}: {e}")
|
||||||
|
raise HTTPException(500, f"Ошибка создания: {str(e)}")
|
||||||
|
|
||||||
@app.delete("/api/servers/{server_name}/files")
|
@app.delete("/api/servers/{server_name}/files")
|
||||||
async def delete_file(server_name: str, path: str, user: dict = Depends(get_current_user)):
|
async def delete_file(server_name: str, path: str, user: dict = Depends(get_current_user)):
|
||||||
if not check_server_access(user, server_name):
|
if not check_server_access(user, server_name):
|
||||||
@@ -1098,6 +1163,82 @@ async def rename_file(server_name: str, old_path: str, new_name: str, user: dict
|
|||||||
old_file_path.rename(new_file_path)
|
old_file_path.rename(new_file_path)
|
||||||
return {"message": "Файл переименован"}
|
return {"message": "Файл переименован"}
|
||||||
|
|
||||||
|
@app.post("/api/servers/{server_name}/files/move")
|
||||||
|
async def move_file(server_name: str, data: dict, user: dict = Depends(get_current_user)):
|
||||||
|
"""Переместить файл или папку"""
|
||||||
|
if not check_server_access(user, server_name):
|
||||||
|
raise HTTPException(403, "Нет доступа к этому серверу")
|
||||||
|
|
||||||
|
source_path = data.get("source", "").strip()
|
||||||
|
destination_path = data.get("destination", "").strip()
|
||||||
|
|
||||||
|
if not source_path:
|
||||||
|
raise HTTPException(400, "Не указан исходный путь")
|
||||||
|
|
||||||
|
server_path = SERVERS_DIR / server_name
|
||||||
|
source_full = server_path / source_path
|
||||||
|
|
||||||
|
# Формируем путь назначения
|
||||||
|
if destination_path:
|
||||||
|
# Извлекаем имя файла из source_path
|
||||||
|
file_name = source_full.name
|
||||||
|
dest_full = server_path / destination_path / file_name
|
||||||
|
else:
|
||||||
|
# Перемещение в корень
|
||||||
|
file_name = source_full.name
|
||||||
|
dest_full = server_path / file_name
|
||||||
|
|
||||||
|
print(f"Moving: {source_full} -> {dest_full}")
|
||||||
|
|
||||||
|
# Проверки безопасности
|
||||||
|
if not source_full.exists():
|
||||||
|
raise HTTPException(404, "Исходный файл не найден")
|
||||||
|
|
||||||
|
if not str(source_full).startswith(str(server_path)):
|
||||||
|
raise HTTPException(400, "Недопустимый исходный путь")
|
||||||
|
|
||||||
|
if not str(dest_full).startswith(str(server_path)):
|
||||||
|
raise HTTPException(400, "Недопустимый путь назначения")
|
||||||
|
|
||||||
|
if dest_full.exists():
|
||||||
|
raise HTTPException(400, "Файл с таким именем уже существует в папке назначения")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Создаем папку назначения если не существует
|
||||||
|
dest_full.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Перемещаем файл/папку
|
||||||
|
import shutil
|
||||||
|
shutil.move(str(source_full), str(dest_full))
|
||||||
|
|
||||||
|
print(f"Moved successfully: {dest_full}")
|
||||||
|
return {"message": "Файл перемещен", "new_path": str(dest_full)}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error moving file: {e}")
|
||||||
|
raise HTTPException(500, f"Ошибка перемещения: {str(e)}")
|
||||||
|
|
||||||
|
@app.put("/api/servers/{server_name}/files/rename")
|
||||||
|
async def rename_file(server_name: str, old_path: str, new_name: str, user: dict = Depends(get_current_user)):
|
||||||
|
if not check_server_access(user, server_name):
|
||||||
|
raise HTTPException(403, "Нет доступа к этому серверу")
|
||||||
|
|
||||||
|
server_path = SERVERS_DIR / server_name
|
||||||
|
old_file_path = server_path / old_path
|
||||||
|
|
||||||
|
if not old_file_path.exists() or not str(old_file_path).startswith(str(server_path)):
|
||||||
|
raise HTTPException(404, "Файл не найден")
|
||||||
|
|
||||||
|
new_file_path = old_file_path.parent / new_name
|
||||||
|
|
||||||
|
if new_file_path.exists():
|
||||||
|
raise HTTPException(400, "Файл с таким именем уже существует")
|
||||||
|
|
||||||
|
if not str(new_file_path).startswith(str(server_path)):
|
||||||
|
raise HTTPException(400, "Недопустимое имя файла")
|
||||||
|
|
||||||
|
old_file_path.rename(new_file_path)
|
||||||
|
return {"message": "Файл переименован"}
|
||||||
|
|
||||||
# API для тикетов
|
# API для тикетов
|
||||||
@app.get("/api/tickets")
|
@app.get("/api/tickets")
|
||||||
async def get_tickets(user: dict = Depends(get_current_user)):
|
async def get_tickets(user: dict = Depends(get_current_user)):
|
||||||
|
|||||||
@@ -1,129 +1 @@
|
|||||||
{
|
{}
|
||||||
"1": {
|
|
||||||
"id": "1",
|
|
||||||
"title": "Пошёл нахуй",
|
|
||||||
"description": "Свин",
|
|
||||||
"author": "arkonsad",
|
|
||||||
"status": "closed",
|
|
||||||
"created_at": "2026-01-14T15:20:26.344010",
|
|
||||||
"updated_at": "2026-01-14T15:22:02.654579",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"author": "arkonsad",
|
|
||||||
"text": "Свин",
|
|
||||||
"timestamp": "2026-01-14T15:20:26.344010"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "Sofa12345",
|
|
||||||
"text": "Ты че",
|
|
||||||
"timestamp": "2026-01-14T15:21:19.943424"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "Sofa12345",
|
|
||||||
"text": "ахуел",
|
|
||||||
"timestamp": "2026-01-14T15:21:24.251787"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "arkonsad",
|
|
||||||
"text": "покушай говна",
|
|
||||||
"timestamp": "2026-01-14T15:21:46.676746"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "system",
|
|
||||||
"text": "Статус изменён на: В работе",
|
|
||||||
"timestamp": "2026-01-14T15:21:48.504108"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "Sofa12345",
|
|
||||||
"text": "тварина ты ебаная",
|
|
||||||
"timestamp": "2026-01-14T15:21:58.245227"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "system",
|
|
||||||
"text": "Статус изменён на: Закрыт",
|
|
||||||
"timestamp": "2026-01-14T15:22:02.654579"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"id": "2",
|
|
||||||
"title": "Разраб даун",
|
|
||||||
"description": "помогите разраб минды даун, а киро вообще маньяк на коммиты в гитею",
|
|
||||||
"author": "MihailPrud",
|
|
||||||
"status": "closed",
|
|
||||||
"created_at": "2026-01-15T03:25:33.660528",
|
|
||||||
"updated_at": "2026-01-15T03:27:41.117949",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"author": "MihailPrud",
|
|
||||||
"text": "помогите разраб минды даун, а киро вообще маньяк на коммиты в гитею",
|
|
||||||
"timestamp": "2026-01-15T03:25:33.660528"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "system",
|
|
||||||
"text": "Статус изменён на: В работе",
|
|
||||||
"timestamp": "2026-01-15T03:25:56.445796"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "Sofa12345",
|
|
||||||
"text": "Дааааа, туда этого бота",
|
|
||||||
"timestamp": "2026-01-15T03:25:58.592839"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "MihailPrud",
|
|
||||||
"text": "памагете",
|
|
||||||
"timestamp": "2026-01-15T03:26:20.740325"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "Sofa12345",
|
|
||||||
"text": "чим",
|
|
||||||
"timestamp": "2026-01-15T03:26:29.038071"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "MihailPrud",
|
|
||||||
"text": "у миня -30 и минет в школу надоть",
|
|
||||||
"timestamp": "2026-01-15T03:26:37.692369"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "Sofa12345",
|
|
||||||
"text": "пиздец нахуй блять",
|
|
||||||
"timestamp": "2026-01-15T03:26:48.846565"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "MihailPrud",
|
|
||||||
"text": "согласен",
|
|
||||||
"timestamp": "2026-01-15T03:26:56.324587"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "Sofa12345",
|
|
||||||
"text": "Nahyi eto school nyxna",
|
|
||||||
"timestamp": "2026-01-15T03:27:15.968192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "Sofa12345",
|
|
||||||
"text": "pizdets",
|
|
||||||
"timestamp": "2026-01-15T03:27:21.810953"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "MihailPrud",
|
|
||||||
"text": "не нужна",
|
|
||||||
"timestamp": "2026-01-15T03:27:24.548623"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "MihailPrud",
|
|
||||||
"text": "но ходить надоть",
|
|
||||||
"timestamp": "2026-01-15T03:27:31.625634"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "system",
|
|
||||||
"text": "Статус изменён на: Закрыт",
|
|
||||||
"timestamp": "2026-01-15T03:27:38.480740"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"author": "MihailPrud",
|
|
||||||
"text": "для баланса вселеннной",
|
|
||||||
"timestamp": "2026-01-15T03:27:41.117949"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,15 +11,10 @@
|
|||||||
"arkonsad": {
|
"arkonsad": {
|
||||||
"username": "arkonsad",
|
"username": "arkonsad",
|
||||||
"password": "$2b$12$z.AYkfa/MlTYFd9rLNfBmu9JHOFKUe8YdddnqCmRqAxc7vGQeo392",
|
"password": "$2b$12$z.AYkfa/MlTYFd9rLNfBmu9JHOFKUe8YdddnqCmRqAxc7vGQeo392",
|
||||||
"role": "banned",
|
"role": "user",
|
||||||
"servers": [
|
"servers": [
|
||||||
"123"
|
"123",
|
||||||
|
"sdfsdf"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"Sofa12345": {
|
|
||||||
"username": "Sofa12345",
|
|
||||||
"password": "$2b$12$Fph20p2mwgOAqoT77wSA3.n1S7NiHLa28aiNOwWcz3PfNhgC5pp5.",
|
|
||||||
"role": "admin",
|
|
||||||
"servers": []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import Profile from './components/Profile';
|
|||||||
import Auth from './components/Auth';
|
import Auth from './components/Auth';
|
||||||
import ErrorBoundary from './components/ErrorBoundary';
|
import ErrorBoundary from './components/ErrorBoundary';
|
||||||
import ThemeSelector from './components/ThemeSelector';
|
import ThemeSelector from './components/ThemeSelector';
|
||||||
|
import NotificationSystem, { notify } from './components/NotificationSystem';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { API_URL } from './config';
|
import { API_URL } from './config';
|
||||||
import { getTheme } from './themes';
|
import { getTheme } from './themes';
|
||||||
@@ -27,7 +28,7 @@ function App() {
|
|||||||
const [showProfile, setShowProfile] = useState(false);
|
const [showProfile, setShowProfile] = useState(false);
|
||||||
const [viewingUsername, setViewingUsername] = useState(null);
|
const [viewingUsername, setViewingUsername] = useState(null);
|
||||||
const [connectionError, setConnectionError] = useState(false);
|
const [connectionError, setConnectionError] = useState(false);
|
||||||
const [theme, setTheme] = useState(localStorage.getItem('theme') || 'dark');
|
const [theme, setTheme] = useState(localStorage.getItem('theme') || 'modern');
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||||
|
|
||||||
const currentTheme = getTheme(theme);
|
const currentTheme = getTheme(theme);
|
||||||
@@ -149,11 +150,13 @@ function App() {
|
|||||||
{ headers: { Authorization: `Bearer ${token}` } }
|
{ headers: { Authorization: `Bearer ${token}` } }
|
||||||
);
|
);
|
||||||
console.log('Сервер запущен:', response.data);
|
console.log('Сервер запущен:', response.data);
|
||||||
|
notify('success', 'Сервер запущен', `Сервер "${serverName}" успешно запущен`);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loadServers();
|
loadServers();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка запуска сервера:', error);
|
console.error('Ошибка запуска сервера:', error);
|
||||||
|
notify('error', 'Ошибка запуска', error.response?.data?.detail || 'Не удалось запустить сервер');
|
||||||
alert(error.response?.data?.detail || 'Ошибка запуска сервера');
|
alert(error.response?.data?.detail || 'Ошибка запуска сервера');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -166,11 +169,13 @@ function App() {
|
|||||||
{ headers: { Authorization: `Bearer ${token}` } }
|
{ headers: { Authorization: `Bearer ${token}` } }
|
||||||
);
|
);
|
||||||
console.log('Сервер остановлен:', response.data);
|
console.log('Сервер остановлен:', response.data);
|
||||||
|
notify('info', 'Сервер остановлен', `Сервер "${serverName}" успешно остановлен`);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loadServers();
|
loadServers();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка остановки сервера:', error);
|
console.error('Ошибка остановки сервера:', error);
|
||||||
|
notify('error', 'Ошибка остановки', error.response?.data?.detail || 'Не удалось остановить сервер');
|
||||||
alert(error.response?.data?.detail || 'Ошибка остановки сервера');
|
alert(error.response?.data?.detail || 'Ошибка остановки сервера');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -334,6 +339,7 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen ${currentTheme.primary} ${currentTheme.text} transition-colors duration-300`}>
|
<div className={`min-h-screen ${currentTheme.primary} ${currentTheme.text} transition-colors duration-300`}>
|
||||||
|
<NotificationSystem theme={currentTheme} />
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className={`${currentTheme.secondary} ${currentTheme.border} border-b backdrop-blur-sm bg-opacity-95 sticky top-0 z-40`}>
|
<header className={`${currentTheme.secondary} ${currentTheme.border} border-b backdrop-blur-sm bg-opacity-95 sticky top-0 z-40`}>
|
||||||
<div className="px-6 py-4">
|
<div className="px-6 py-4">
|
||||||
@@ -486,6 +492,42 @@ function App() {
|
|||||||
<main className="flex-1 flex flex-col overflow-hidden">
|
<main className="flex-1 flex flex-col overflow-hidden">
|
||||||
{selectedServer ? (
|
{selectedServer ? (
|
||||||
<>
|
<>
|
||||||
|
{/* Server Header with Controls */}
|
||||||
|
<div className={`${currentTheme.secondary} ${currentTheme.border} border-b px-6 py-3 flex items-center justify-between`}>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Server className="w-5 h-5" />
|
||||||
|
<span className="font-semibold text-lg">
|
||||||
|
{servers.find(s => s.name === selectedServer)?.displayName || selectedServer}
|
||||||
|
</span>
|
||||||
|
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
||||||
|
servers.find(s => s.name === selectedServer)?.status === 'running'
|
||||||
|
? 'bg-green-600 text-white'
|
||||||
|
: 'bg-gray-600 text-white'
|
||||||
|
}`}>
|
||||||
|
{servers.find(s => s.name === selectedServer)?.status === 'running' ? 'Запущен' : 'Остановлен'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{servers.find(s => s.name === selectedServer)?.status === 'stopped' ? (
|
||||||
|
<button
|
||||||
|
onClick={() => startServer(selectedServer)}
|
||||||
|
className="bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 text-white transition shadow-lg"
|
||||||
|
>
|
||||||
|
<Play className="w-4 h-4" />
|
||||||
|
Запустить
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={() => stopServer(selectedServer)}
|
||||||
|
className="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 text-white transition shadow-lg"
|
||||||
|
>
|
||||||
|
<Square className="w-4 h-4" />
|
||||||
|
Сброс
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className={`${currentTheme.secondary} ${currentTheme.border} border-b flex overflow-x-auto`}>
|
<div className={`${currentTheme.secondary} ${currentTheme.border} border-b flex overflow-x-auto`}>
|
||||||
{[
|
{[
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default function Auth({ onLogin }) {
|
|||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [theme] = useState(localStorage.getItem('theme') || 'dark');
|
const [theme] = useState(localStorage.getItem('theme') || 'modern');
|
||||||
const [oidcProviders, setOidcProviders] = useState({});
|
const [oidcProviders, setOidcProviders] = useState({});
|
||||||
|
|
||||||
const currentTheme = getTheme(theme);
|
const currentTheme = getTheme(theme);
|
||||||
@@ -182,7 +182,7 @@ export default function Auth({ onLogin }) {
|
|||||||
{isLogin && (
|
{isLogin && (
|
||||||
<div className={`mt-6 text-center text-sm ${currentTheme.textSecondary}`}>
|
<div className={`mt-6 text-center text-sm ${currentTheme.textSecondary}`}>
|
||||||
<p>Учётные данные по умолчанию:</p>
|
<p>Учётные данные по умолчанию:</p>
|
||||||
<p className={`${currentTheme.text} font-mono mt-1`}>Sofa12345 / arkonsad123</p>
|
<p className={`${currentTheme.text} font-mono mt-1`}>none / none</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -54,32 +54,63 @@ export default function Console({ serverName, token, theme }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Функция для раскраски логов
|
||||||
|
const colorizeLog = (log) => {
|
||||||
|
// INFO - зеленый
|
||||||
|
if (log.includes('[INFO]') || log.includes('Done (')) {
|
||||||
|
return <span className="text-green-400">{log}</span>;
|
||||||
|
}
|
||||||
|
// WARN - желтый
|
||||||
|
if (log.includes('[WARN]') || log.includes('WARNING')) {
|
||||||
|
return <span className="text-yellow-400">{log}</span>;
|
||||||
|
}
|
||||||
|
// ERROR - красный
|
||||||
|
if (log.includes('[ERROR]') || log.includes('Exception')) {
|
||||||
|
return <span className="text-red-400">{log}</span>;
|
||||||
|
}
|
||||||
|
// Время - серый
|
||||||
|
if (log.match(/^\[\d{2}:\d{2}:\d{2}\]/)) {
|
||||||
|
const time = log.match(/^\[\d{2}:\d{2}:\d{2}\]/)[0];
|
||||||
|
const rest = log.substring(time.length);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="text-gray-500">{time}</span>
|
||||||
|
<span className="text-gray-300">{rest}</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Обычный текст
|
||||||
|
return <span className="text-gray-300">{log}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col h-full ${theme.primary}`}>
|
<div className={`flex flex-col h-full ${theme.primary}`}>
|
||||||
<div className={`flex-1 overflow-y-auto p-4 font-mono text-sm ${theme.secondary}`}>
|
{/* Консоль */}
|
||||||
|
<div className={`flex-1 overflow-y-auto p-4 font-mono text-sm ${theme.console || theme.secondary}`}>
|
||||||
{logs.length === 0 ? (
|
{logs.length === 0 ? (
|
||||||
<div className={theme.textSecondary}>Консоль пуста. Запустите сервер для просмотра логов.</div>
|
<div className={theme.textSecondary}>Консоль пуста. Запустите сервер для просмотра логов.</div>
|
||||||
) : (
|
) : (
|
||||||
logs.map((log, index) => (
|
logs.map((log, index) => (
|
||||||
<div key={index} className={`${theme.text} whitespace-pre-wrap leading-relaxed`}>
|
<div key={index} className="whitespace-pre-wrap leading-relaxed">
|
||||||
{log}
|
{colorizeLog(log)}
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
<div ref={logsEndRef} />
|
<div ref={logsEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Поле ввода команды */}
|
||||||
<form onSubmit={sendCommand} className={`${theme.border} border-t p-4 flex gap-2`}>
|
<form onSubmit={sendCommand} className={`${theme.border} border-t p-4 flex gap-2`}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={command}
|
value={command}
|
||||||
onChange={(e) => setCommand(e.target.value)}
|
onChange={(e) => setCommand(e.target.value)}
|
||||||
placeholder="Введите команду..."
|
placeholder="Введите команду и нажмите Enter для отправки, используйте стрелки для навигации между предыдущими командами"
|
||||||
className={`flex-1 ${theme.input} ${theme.border} border rounded-xl px-4 py-2 ${theme.text} focus:outline-none focus:ring-2 focus:ring-blue-500 transition`}
|
className={`flex-1 ${theme.input} ${theme.border} border rounded-lg px-4 py-2.5 ${theme.text} placeholder:text-gray-600 focus:outline-none focus:ring-2 focus:ring-green-500 transition`}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className={`${theme.accent} ${theme.accentHover} px-6 py-2 rounded-xl flex items-center gap-2 text-white transition`}
|
className={`${theme.success} ${theme.successHover} px-6 py-2.5 rounded-lg flex items-center gap-2 text-white font-medium transition shadow-lg`}
|
||||||
>
|
>
|
||||||
<Send className="w-4 h-4" />
|
<Send className="w-4 h-4" />
|
||||||
Отправить
|
Отправить
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
|||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { API_URL } from '../config';
|
import { API_URL } from '../config';
|
||||||
|
import { notify } from './NotificationSystem';
|
||||||
|
|
||||||
export default function CreateServerModal({ token, theme, onClose, onCreated }) {
|
export default function CreateServerModal({ token, theme, onClose, onCreated }) {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
@@ -21,9 +22,11 @@ export default function CreateServerModal({ token, theme, onClose, onCreated })
|
|||||||
formData,
|
formData,
|
||||||
{ headers: { Authorization: `Bearer ${token}` } }
|
{ headers: { Authorization: `Bearer ${token}` } }
|
||||||
);
|
);
|
||||||
|
notify('success', 'Сервер создан', `Сервер "${formData.displayName}" успешно создан`);
|
||||||
onCreated();
|
onCreated();
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify('error', 'Ошибка создания', error.response?.data?.detail || 'Не удалось создать сервер');
|
||||||
alert(error.response?.data?.detail || 'Ошибка создания сервера');
|
alert(error.response?.data?.detail || 'Ошибка создания сервера');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { X, Save } from 'lucide-react';
|
import { X, Save } from 'lucide-react';
|
||||||
|
|
||||||
export default function FileEditorModal({ file, onClose, onSave }) {
|
export default function FileEditorModal({ file, onClose, onSave, theme }) {
|
||||||
const [content, setContent] = useState(file.content);
|
const [content, setContent] = useState(file.content);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
@@ -25,37 +25,37 @@ export default function FileEditorModal({ file, onClose, onSave }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div className="bg-gray-800 rounded-lg w-full max-w-4xl h-[80vh] flex flex-col">
|
<div className={`${theme.secondary} rounded-lg w-full max-w-4xl h-[80vh] flex flex-col ${theme.border} border`}>
|
||||||
<div className="flex items-center justify-between p-4 border-b border-gray-700">
|
<div className={`flex items-center justify-between p-4 ${theme.border} border-b`}>
|
||||||
<h2 className="text-xl font-bold">Редактирование: {file.name}</h2>
|
<h2 className={`text-xl font-bold ${theme.text}`}>Редактирование: {file.name}</h2>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
className="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded flex items-center gap-2 disabled:opacity-50"
|
className="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded flex items-center gap-2 disabled:opacity-50 text-white transition"
|
||||||
>
|
>
|
||||||
<Save className="w-4 h-4" />
|
<Save className="w-4 h-4" />
|
||||||
{saving ? 'Сохранение...' : 'Сохранить'}
|
{saving ? 'Сохранение...' : 'Сохранить'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="text-gray-400 hover:text-white"
|
className={`${theme.textSecondary} hover:${theme.text} transition`}
|
||||||
>
|
>
|
||||||
<X className="w-6 h-6" />
|
<X className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-hidden p-4 bg-gray-900">
|
<div className={`flex-1 overflow-hidden p-4 ${theme.console || theme.primary}`}>
|
||||||
<textarea
|
<textarea
|
||||||
value={content}
|
value={content}
|
||||||
onChange={(e) => setContent(e.target.value)}
|
onChange={(e) => setContent(e.target.value)}
|
||||||
className="w-full h-full bg-black text-gray-300 font-mono text-sm p-4 rounded border border-gray-700 focus:outline-none focus:border-blue-500 resize-none"
|
className={`w-full h-full ${theme.console || theme.primary} ${theme.consoleText || theme.text} font-mono text-sm p-4 rounded ${theme.border} border focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none`}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 border-t border-gray-700 text-sm text-gray-400">
|
<div className={`p-4 ${theme.border} border-t text-sm ${theme.textSecondary}`}>
|
||||||
Используйте Ctrl+S для быстрого сохранения
|
Используйте Ctrl+S для быстрого сохранения
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,22 +1,42 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Folder, File, Download, Trash2, Upload, Edit, Eye } from 'lucide-react';
|
import { Folder, File, Download, Trash2, Upload, Edit, Eye, Search } from 'lucide-react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import FileEditorModal from './FileEditorModal';
|
import FileEditorModal from './FileEditorModal';
|
||||||
import FileViewerModal from './FileViewerModal';
|
import FileViewerModal from './FileViewerModal';
|
||||||
import { API_URL } from '../config';
|
import { API_URL } from '../config';
|
||||||
|
import { notify } from './NotificationSystem';
|
||||||
|
|
||||||
export default function FileManager({ serverName, token }) {
|
export default function FileManager({ serverName, token, theme }) {
|
||||||
const [files, setFiles] = useState([]);
|
const [files, setFiles] = useState([]);
|
||||||
const [currentPath, setCurrentPath] = useState('');
|
const [currentPath, setCurrentPath] = useState('');
|
||||||
const [editingFile, setEditingFile] = useState(null);
|
const [editingFile, setEditingFile] = useState(null);
|
||||||
const [viewingFile, setViewingFile] = useState(null);
|
const [viewingFile, setViewingFile] = useState(null);
|
||||||
const [renamingFile, setRenamingFile] = useState(null);
|
const [renamingFile, setRenamingFile] = useState(null);
|
||||||
const [newFileName, setNewFileName] = useState('');
|
const [newFileName, setNewFileName] = useState('');
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [selectedFiles, setSelectedFiles] = useState([]);
|
||||||
|
const [selectAll, setSelectAll] = useState(false);
|
||||||
|
const [showNewMenu, setShowNewMenu] = useState(false);
|
||||||
|
const [creatingNew, setCreatingNew] = useState(null); // 'file' or 'folder'
|
||||||
|
const [newItemName, setNewItemName] = useState('');
|
||||||
|
const [cutFiles, setCutFiles] = useState([]); // Файлы для перемещения
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadFiles();
|
loadFiles();
|
||||||
}, [serverName, currentPath]);
|
}, [serverName, currentPath]);
|
||||||
|
|
||||||
|
// Закрытие меню "Новый" при клике вне его
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (e) => {
|
||||||
|
if (showNewMenu && !e.target.closest('.new-menu-container')) {
|
||||||
|
setShowNewMenu(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
}, [showNewMenu]);
|
||||||
|
|
||||||
const loadFiles = async () => {
|
const loadFiles = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(`${API_URL}/api/servers/${serverName}/files`, {
|
const { data } = await axios.get(`${API_URL}/api/servers/${serverName}/files`, {
|
||||||
@@ -53,8 +73,10 @@ export default function FileManager({ serverName, token }) {
|
|||||||
params: { path: filePath },
|
params: { path: filePath },
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
notify('success', 'Файл удален', `"${fileName}" успешно удален`);
|
||||||
loadFiles();
|
loadFiles();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify('error', 'Ошибка удаления', 'Не удалось удалить файл');
|
||||||
alert('Ошибка удаления файла');
|
alert('Ошибка удаления файла');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -72,8 +94,10 @@ export default function FileManager({ serverName, token }) {
|
|||||||
formData,
|
formData,
|
||||||
{ headers: { Authorization: `Bearer ${token}` } }
|
{ headers: { Authorization: `Bearer ${token}` } }
|
||||||
);
|
);
|
||||||
|
notify('success', 'Файл загружен', `"${file.name}" успешно загружен`);
|
||||||
loadFiles();
|
loadFiles();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify('error', 'Ошибка загрузки', 'Не удалось загрузить файл');
|
||||||
alert('Ошибка загрузки файла');
|
alert('Ошибка загрузки файла');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -115,8 +139,10 @@ export default function FileManager({ serverName, token }) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
setEditingFile(null);
|
setEditingFile(null);
|
||||||
|
notify('success', 'Файл сохранен', 'Изменения успешно сохранены');
|
||||||
alert('Файл сохранен');
|
alert('Файл сохранен');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify('error', 'Ошибка сохранения', error.response?.data?.detail || 'Не удалось сохранить файл');
|
||||||
alert(error.response?.data?.detail || 'Ошибка сохранения файла');
|
alert(error.response?.data?.detail || 'Ошибка сохранения файла');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -144,8 +170,10 @@ export default function FileManager({ serverName, token }) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
setRenamingFile(null);
|
setRenamingFile(null);
|
||||||
|
notify('success', 'Файл переименован', `"${oldName}" → "${newFileName}"`);
|
||||||
loadFiles();
|
loadFiles();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify('error', 'Ошибка переименования', error.response?.data?.detail || 'Не удалось переименовать файл');
|
||||||
alert(error.response?.data?.detail || 'Ошибка переименования файла');
|
alert(error.response?.data?.detail || 'Ошибка переименования файла');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -158,45 +186,408 @@ export default function FileManager({ serverName, token }) {
|
|||||||
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filteredFiles = files.filter(file =>
|
||||||
|
file.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Выбор всех файлов
|
||||||
|
const handleSelectAll = () => {
|
||||||
|
if (selectAll) {
|
||||||
|
setSelectedFiles([]);
|
||||||
|
} else {
|
||||||
|
setSelectedFiles(filteredFiles.map(f => f.name));
|
||||||
|
}
|
||||||
|
setSelectAll(!selectAll);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Выбор отдельного файла
|
||||||
|
const handleSelectFile = (fileName) => {
|
||||||
|
if (selectedFiles.includes(fileName)) {
|
||||||
|
setSelectedFiles(selectedFiles.filter(f => f !== fileName));
|
||||||
|
} else {
|
||||||
|
setSelectedFiles([...selectedFiles, fileName]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Создание нового файла
|
||||||
|
const createNewFile = async () => {
|
||||||
|
if (!newItemName.trim()) {
|
||||||
|
alert('Введите имя файла');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Creating file:', {
|
||||||
|
type: 'file',
|
||||||
|
name: newItemName,
|
||||||
|
path: currentPath
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await axios.post(
|
||||||
|
`${API_URL}/api/servers/${serverName}/files/create`,
|
||||||
|
{
|
||||||
|
type: 'file',
|
||||||
|
name: newItemName,
|
||||||
|
path: currentPath
|
||||||
|
},
|
||||||
|
{ headers: { Authorization: `Bearer ${token}` } }
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('File created successfully:', response.data);
|
||||||
|
notify('success', 'Файл создан', `"${newItemName}" успешно создан`);
|
||||||
|
setCreatingNew(null);
|
||||||
|
setNewItemName('');
|
||||||
|
loadFiles();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка создания файла:', error);
|
||||||
|
console.error('Error details:', error.response?.data);
|
||||||
|
notify('error', 'Ошибка создания', error.response?.data?.detail || 'Не удалось создать файл');
|
||||||
|
alert(error.response?.data?.detail || 'Ошибка создания файла');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Создание новой папки
|
||||||
|
const createNewFolder = async () => {
|
||||||
|
if (!newItemName.trim()) {
|
||||||
|
alert('Введите имя папки');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Creating folder:', {
|
||||||
|
type: 'folder',
|
||||||
|
name: newItemName,
|
||||||
|
path: currentPath
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await axios.post(
|
||||||
|
`${API_URL}/api/servers/${serverName}/files/create`,
|
||||||
|
{
|
||||||
|
type: 'folder',
|
||||||
|
name: newItemName,
|
||||||
|
path: currentPath
|
||||||
|
},
|
||||||
|
{ headers: { Authorization: `Bearer ${token}` } }
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Folder created successfully:', response.data);
|
||||||
|
notify('success', 'Папка создана', `"${newItemName}" успешно создана`);
|
||||||
|
setCreatingNew(null);
|
||||||
|
setNewItemName('');
|
||||||
|
loadFiles();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка создания папки:', error);
|
||||||
|
console.error('Error details:', error.response?.data);
|
||||||
|
notify('error', 'Ошибка создания', error.response?.data?.detail || 'Не удалось создать папку');
|
||||||
|
alert(error.response?.data?.detail || 'Ошибка создания папки');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Перемещение файла
|
||||||
|
const moveFile = async (sourcePath, destinationPath) => {
|
||||||
|
try {
|
||||||
|
console.log('Moving file:', { sourcePath, destinationPath });
|
||||||
|
|
||||||
|
const response = await axios.post(
|
||||||
|
`${API_URL}/api/servers/${serverName}/files/move`,
|
||||||
|
{
|
||||||
|
source: sourcePath,
|
||||||
|
destination: destinationPath
|
||||||
|
},
|
||||||
|
{ headers: { Authorization: `Bearer ${token}` } }
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('File moved successfully:', response.data);
|
||||||
|
const fileName = sourcePath.split('/').pop();
|
||||||
|
notify('success', 'Файл перемещен', `"${fileName}" успешно перемещен`);
|
||||||
|
loadFiles();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка перемещения файла:', error);
|
||||||
|
notify('error', 'Ошибка перемещения', error.response?.data?.detail || 'Не удалось переместить файл');
|
||||||
|
alert(error.response?.data?.detail || 'Ошибка перемещения файла');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Вырезать файлы
|
||||||
|
const handleCut = () => {
|
||||||
|
if (selectedFiles.length === 0) {
|
||||||
|
alert('Выберите файлы для перемещения');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filesToCut = selectedFiles.map(fileName => {
|
||||||
|
const filePath = currentPath ? `${currentPath}/${fileName}` : fileName;
|
||||||
|
return { name: fileName, path: filePath };
|
||||||
|
});
|
||||||
|
|
||||||
|
setCutFiles(filesToCut);
|
||||||
|
console.log('Files cut:', filesToCut);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Вставить файлы
|
||||||
|
const handlePaste = async () => {
|
||||||
|
if (cutFiles.length === 0) {
|
||||||
|
alert('Нет файлов для вставки');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Перемещаем каждый файл
|
||||||
|
for (const file of cutFiles) {
|
||||||
|
await moveFile(file.path, currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
notify('success', 'Файлы перемещены', `Перемещено файлов: ${cutFiles.length}`);
|
||||||
|
|
||||||
|
// Очищаем список вырезанных файлов
|
||||||
|
setCutFiles([]);
|
||||||
|
setSelectedFiles([]);
|
||||||
|
setSelectAll(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка вставки файлов:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Отмена вырезания
|
||||||
|
const handleCancelCut = () => {
|
||||||
|
setCutFiles([]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col bg-gray-900">
|
<div className={`h-full flex flex-col ${theme.primary}`}>
|
||||||
<div className="border-b border-gray-700 p-4 flex items-center justify-between">
|
{/* Header */}
|
||||||
|
<div className={`${theme.border} border-b p-4`}>
|
||||||
|
<h2 className={`text-xl font-semibold mb-4 ${theme.text}`}>Управление файлами</h2>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{/* Search */}
|
||||||
|
<div className="flex-1 relative">
|
||||||
|
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${theme.textSecondary}`} />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
placeholder="Поиск по названию ф..."
|
||||||
|
className={`w-full ${theme.input} ${theme.border} border rounded-lg pl-10 pr-4 py-2 ${theme.text} placeholder:text-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
|
<label className={`${theme.success} ${theme.successHover} px-4 py-2 rounded-lg cursor-pointer flex items-center gap-2 text-white font-medium transition shadow-lg`}>
|
||||||
|
<Download className="w-4 h-4" />
|
||||||
|
Загрузить
|
||||||
|
<input type="file" onChange={uploadFile} className="hidden" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={loadFiles}
|
||||||
|
className={`${theme.danger} ${theme.dangerHover} px-4 py-2 rounded-lg flex items-center gap-2 text-white font-medium transition shadow-lg`}
|
||||||
|
>
|
||||||
|
Обновить
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Кнопки вырезать/вставить */}
|
||||||
|
<button
|
||||||
|
onClick={handleCut}
|
||||||
|
disabled={selectedFiles.length === 0}
|
||||||
|
className={`bg-orange-600 hover:bg-orange-700 disabled:opacity-50 disabled:cursor-not-allowed px-4 py-2 rounded-lg flex items-center gap-2 text-white font-medium transition shadow-lg`}
|
||||||
|
title="Вырезать выбранные файлы"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 10-4.243 4.243 3 3 0 004.243-4.243zm0-5.758a3 3 0 10-4.243-4.243 3 3 0 004.243 4.243z" />
|
||||||
|
</svg>
|
||||||
|
Вырезать {selectedFiles.length > 0 && `(${selectedFiles.length})`}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handlePaste}
|
||||||
|
disabled={cutFiles.length === 0}
|
||||||
|
className={`bg-purple-600 hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed px-4 py-2 rounded-lg flex items-center gap-2 text-white font-medium transition shadow-lg`}
|
||||||
|
title="Вставить файлы"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||||
|
</svg>
|
||||||
|
Вставить {cutFiles.length > 0 && `(${cutFiles.length})`}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{cutFiles.length > 0 && (
|
||||||
|
<button
|
||||||
|
onClick={handleCancelCut}
|
||||||
|
className="bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-lg flex items-center gap-2 text-white font-medium transition shadow-lg"
|
||||||
|
title="Отменить вырезание"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="relative new-menu-container">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowNewMenu(!showNewMenu)}
|
||||||
|
className="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg flex items-center gap-2 text-white font-medium transition shadow-lg"
|
||||||
|
>
|
||||||
|
Новый
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showNewMenu && (
|
||||||
|
<div className={`absolute right-0 mt-2 w-48 ${theme.secondary} rounded-lg shadow-xl ${theme.border} border z-10`}>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setCreatingNew('file');
|
||||||
|
setShowNewMenu(false);
|
||||||
|
setNewItemName('');
|
||||||
|
}}
|
||||||
|
className={`w-full text-left px-4 py-2 ${theme.hover} ${theme.text} transition rounded-t-lg`}
|
||||||
|
>
|
||||||
|
📄 Создать файл
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setCreatingNew('folder');
|
||||||
|
setShowNewMenu(false);
|
||||||
|
setNewItemName('');
|
||||||
|
}}
|
||||||
|
className={`w-full text-left px-4 py-2 ${theme.hover} ${theme.text} transition rounded-b-lg`}
|
||||||
|
>
|
||||||
|
📁 Создать папку
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Path */}
|
||||||
|
<div className={`${theme.secondary} px-4 py-3 ${theme.border} border-b`}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{currentPath && (
|
{currentPath && (
|
||||||
<button
|
<button
|
||||||
onClick={goBack}
|
onClick={goBack}
|
||||||
className="bg-gray-700 hover:bg-gray-600 px-3 py-1 rounded"
|
className={`${theme.hover} px-3 py-1 rounded text-sm ${theme.text} transition`}
|
||||||
>
|
>
|
||||||
← Назад
|
← Назад
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<span className="text-gray-400">/{currentPath || 'root'}</span>
|
<span className={`${theme.textSecondary} font-mono text-sm`}>
|
||||||
|
/{currentPath || ''}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Индикатор вырезанных файлов */}
|
||||||
|
{cutFiles.length > 0 && (
|
||||||
|
<span className="ml-auto text-sm text-orange-400 flex items-center gap-2">
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 10-4.243 4.243 3 3 0 004.243-4.243zm0-5.758a3 3 0 10-4.243-4.243 3 3 0 004.243 4.243z" />
|
||||||
|
</svg>
|
||||||
|
Вырезано файлов: {cutFiles.length}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<label className="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded cursor-pointer flex items-center gap-2">
|
|
||||||
<Upload className="w-4 h-4" />
|
|
||||||
Загрузить
|
|
||||||
<input type="file" onChange={uploadFile} className="hidden" />
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Table */}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-gray-800 sticky top-0">
|
<thead className={`${theme.secondary} sticky top-0 ${theme.border} border-b`}>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left p-4">Имя</th>
|
<th className={`text-left p-4 ${theme.textSecondary} font-medium text-sm`}>
|
||||||
<th className="text-left p-4">Размер</th>
|
<input
|
||||||
<th className="text-right p-4">Действия</th>
|
type="checkbox"
|
||||||
|
className="mr-3 cursor-pointer"
|
||||||
|
checked={selectAll}
|
||||||
|
onChange={handleSelectAll}
|
||||||
|
/>
|
||||||
|
Имя
|
||||||
|
</th>
|
||||||
|
<th className={`text-left p-4 ${theme.textSecondary} font-medium text-sm`}>Тип</th>
|
||||||
|
<th className={`text-left p-4 ${theme.textSecondary} font-medium text-sm`}>Размер</th>
|
||||||
|
<th className={`text-left p-4 ${theme.textSecondary} font-medium text-sm`}>Последнее изменение</th>
|
||||||
|
<th className={`text-left p-4 ${theme.textSecondary} font-medium text-sm`}>Разрешение</th>
|
||||||
|
<th className={`text-right p-4 ${theme.textSecondary} font-medium text-sm`}>Действия</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{files.map((file) => (
|
{/* Форма создания нового файла/папки */}
|
||||||
|
{creatingNew && (
|
||||||
|
<tr className={`${theme.border} border-b bg-blue-900 bg-opacity-20`}>
|
||||||
|
<td className="p-4" colSpan="6">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{creatingNew === 'file' ? (
|
||||||
|
<File className="w-5 h-5 text-gray-400" />
|
||||||
|
) : (
|
||||||
|
<Folder className="w-5 h-5 text-blue-400" />
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newItemName}
|
||||||
|
onChange={(e) => setNewItemName(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
creatingNew === 'file' ? createNewFile() : createNewFolder();
|
||||||
|
}
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
setCreatingNew(null);
|
||||||
|
setNewItemName('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder={creatingNew === 'file' ? 'Имя файла...' : 'Имя папки...'}
|
||||||
|
autoFocus
|
||||||
|
className={`flex-1 ${theme.input} ${theme.border} border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500`}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={creatingNew === 'file' ? createNewFile : createNewFolder}
|
||||||
|
className="bg-green-600 hover:bg-green-700 px-4 py-2 rounded text-sm text-white transition"
|
||||||
|
>
|
||||||
|
Создать
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setCreatingNew(null);
|
||||||
|
setNewItemName('');
|
||||||
|
}}
|
||||||
|
className={`${theme.danger} ${theme.dangerHover} px-4 py-2 rounded text-sm text-white transition`}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{filteredFiles.length === 0 ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan="6" className="text-center py-12">
|
||||||
|
<div className={theme.textSecondary}>
|
||||||
|
<Folder className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||||
|
<p>No data</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
filteredFiles.map((file) => {
|
||||||
|
const isCut = cutFiles.some(f => f.name === file.name);
|
||||||
|
|
||||||
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={file.name}
|
key={file.name}
|
||||||
className="border-b border-gray-800 hover:bg-gray-800"
|
className={`${theme.border} border-b ${theme.hover} transition ${
|
||||||
|
isCut ? 'opacity-50 bg-orange-900 bg-opacity-20' : ''
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<td className="p-4">
|
<td className="p-4">
|
||||||
{renamingFile === file.name ? (
|
{renamingFile === file.name ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedFiles.includes(file.name)}
|
||||||
|
onChange={() => handleSelectFile(file.name)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
{file.type === 'directory' ? (
|
{file.type === 'directory' ? (
|
||||||
<Folder className="w-5 h-5 text-blue-400" />
|
<Folder className="w-5 h-5 text-blue-400" />
|
||||||
) : (
|
) : (
|
||||||
@@ -212,7 +603,7 @@ export default function FileManager({ serverName, token }) {
|
|||||||
if (e.key === 'Escape') setRenamingFile(null);
|
if (e.key === 'Escape') setRenamingFile(null);
|
||||||
}}
|
}}
|
||||||
autoFocus
|
autoFocus
|
||||||
className="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-sm focus:outline-none focus:border-blue-500"
|
className={`${theme.input} ${theme.border} border rounded px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -221,53 +612,57 @@ export default function FileManager({ serverName, token }) {
|
|||||||
onClick={() => file.type === 'directory' && openFolder(file.name)}
|
onClick={() => file.type === 'directory' && openFolder(file.name)}
|
||||||
onDoubleClick={() => file.type === 'file' && viewFile(file.name)}
|
onDoubleClick={() => file.type === 'file' && viewFile(file.name)}
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedFiles.includes(file.name)}
|
||||||
|
onChange={() => handleSelectFile(file.name)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
{file.type === 'directory' ? (
|
{file.type === 'directory' ? (
|
||||||
<Folder className="w-5 h-5 text-blue-400" />
|
<Folder className="w-5 h-5 text-blue-400" />
|
||||||
) : (
|
) : (
|
||||||
<File className="w-5 h-5 text-gray-400" />
|
<File className="w-5 h-5 text-gray-400" />
|
||||||
)}
|
)}
|
||||||
<span>{file.name}</span>
|
<span className={theme.text}>{file.name}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="p-4 text-gray-400">{formatSize(file.size)}</td>
|
<td className={`p-4 ${theme.textSecondary} text-sm`}>
|
||||||
|
{file.type === 'directory' ? 'Папка' : 'Файл'}
|
||||||
|
</td>
|
||||||
|
<td className={`p-4 ${theme.textSecondary} text-sm`}>{formatSize(file.size)}</td>
|
||||||
|
<td className={`p-4 ${theme.textSecondary} text-sm`}>-</td>
|
||||||
|
<td className={`p-4 ${theme.textSecondary} text-sm`}>-</td>
|
||||||
<td className="p-4">
|
<td className="p-4">
|
||||||
<div className="flex gap-2 justify-end">
|
<div className="flex gap-2 justify-end">
|
||||||
{file.type === 'file' && (
|
{file.type === 'file' && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => viewFile(file.name)}
|
onClick={() => viewFile(file.name)}
|
||||||
className="bg-blue-600 hover:bg-blue-700 p-2 rounded"
|
className={`${theme.card} ${theme.hover} p-2 rounded transition`}
|
||||||
title="Просмотр"
|
title="Просмотр"
|
||||||
>
|
>
|
||||||
<Eye className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => editFile(file.name)}
|
onClick={() => editFile(file.name)}
|
||||||
className="bg-purple-600 hover:bg-purple-700 p-2 rounded"
|
className={`${theme.card} ${theme.hover} p-2 rounded transition`}
|
||||||
title="Редактировать"
|
title="Редактировать"
|
||||||
>
|
>
|
||||||
<Edit className="w-4 h-4" />
|
<Edit className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => downloadFile(file.name)}
|
onClick={() => downloadFile(file.name)}
|
||||||
className="bg-green-600 hover:bg-green-700 p-2 rounded"
|
className={`${theme.card} ${theme.hover} p-2 rounded transition`}
|
||||||
title="Скачать"
|
title="Скачать"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4" />
|
<Download className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<button
|
|
||||||
onClick={() => startRename(file.name)}
|
|
||||||
className="bg-yellow-600 hover:bg-yellow-700 p-2 rounded"
|
|
||||||
title="Переименовать"
|
|
||||||
>
|
|
||||||
<Edit className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteFile(file.name)}
|
onClick={() => deleteFile(file.name)}
|
||||||
className="bg-red-600 hover:bg-red-700 p-2 rounded"
|
className={`${theme.card} ${theme.hover} p-2 rounded text-red-400 transition`}
|
||||||
title="Удалить"
|
title="Удалить"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-4 h-4" />
|
||||||
@@ -275,7 +670,9 @@ export default function FileManager({ serverName, token }) {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -288,6 +685,7 @@ export default function FileManager({ serverName, token }) {
|
|||||||
setEditingFile(viewingFile);
|
setEditingFile(viewingFile);
|
||||||
setViewingFile(null);
|
setViewingFile(null);
|
||||||
}}
|
}}
|
||||||
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -296,6 +694,7 @@ export default function FileManager({ serverName, token }) {
|
|||||||
file={editingFile}
|
file={editingFile}
|
||||||
onClose={() => setEditingFile(null)}
|
onClose={() => setEditingFile(null)}
|
||||||
onSave={saveFile}
|
onSave={saveFile}
|
||||||
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import { X, Edit } from 'lucide-react';
|
import { X, Edit } from 'lucide-react';
|
||||||
|
|
||||||
export default function FileViewerModal({ file, onClose, onEdit }) {
|
export default function FileViewerModal({ file, onClose, onEdit, theme }) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div className="bg-gray-800 rounded-lg w-full max-w-4xl h-[80vh] flex flex-col">
|
<div className={`${theme.secondary} rounded-lg w-full max-w-4xl h-[80vh] flex flex-col ${theme.border} border`}>
|
||||||
<div className="flex items-center justify-between p-4 border-b border-gray-700">
|
<div className={`flex items-center justify-between p-4 ${theme.border} border-b`}>
|
||||||
<h2 className="text-xl font-bold">{file.name}</h2>
|
<h2 className={`text-xl font-bold ${theme.text}`}>{file.name}</h2>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={onEdit}
|
onClick={onEdit}
|
||||||
className="bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded flex items-center gap-2"
|
className="bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded flex items-center gap-2 text-white transition"
|
||||||
>
|
>
|
||||||
<Edit className="w-4 h-4" />
|
<Edit className="w-4 h-4" />
|
||||||
Редактировать
|
Редактировать
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="text-gray-400 hover:text-white"
|
className={`${theme.textSecondary} hover:${theme.text} transition`}
|
||||||
>
|
>
|
||||||
<X className="w-6 h-6" />
|
<X className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto p-4 bg-gray-900">
|
<div className={`flex-1 overflow-auto p-4 ${theme.console || theme.primary}`}>
|
||||||
<pre className="text-sm text-gray-300 font-mono whitespace-pre-wrap">
|
<pre className={`text-sm ${theme.consoleText || theme.text} font-mono whitespace-pre-wrap`}>
|
||||||
{file.content}
|
{file.content}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
95
frontend/src/components/NotificationSystem.jsx
Normal file
95
frontend/src/components/NotificationSystem.jsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function NotificationSystem({ theme }) {
|
||||||
|
const [notifications, setNotifications] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Слушаем события уведомлений
|
||||||
|
const handleNotification = (event) => {
|
||||||
|
const { type, title, message } = event.detail;
|
||||||
|
addNotification(type, title, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('notification', handleNotification);
|
||||||
|
return () => window.removeEventListener('notification', handleNotification);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const addNotification = (type, title, message) => {
|
||||||
|
const id = Date.now();
|
||||||
|
const notification = { id, type, title, message };
|
||||||
|
|
||||||
|
setNotifications(prev => [...prev, notification]);
|
||||||
|
|
||||||
|
// Автоматически удаляем через 5 секунд
|
||||||
|
setTimeout(() => {
|
||||||
|
removeNotification(id);
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeNotification = (id) => {
|
||||||
|
setNotifications(prev => prev.filter(n => n.id !== id));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIcon = (type) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'success':
|
||||||
|
return <CheckCircle className="w-5 h-5" />;
|
||||||
|
case 'error':
|
||||||
|
return <AlertCircle className="w-5 h-5" />;
|
||||||
|
case 'warning':
|
||||||
|
return <AlertTriangle className="w-5 h-5" />;
|
||||||
|
case 'info':
|
||||||
|
default:
|
||||||
|
return <Info className="w-5 h-5" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getColors = (type) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'success':
|
||||||
|
return 'bg-green-600 border-green-500';
|
||||||
|
case 'error':
|
||||||
|
return 'bg-red-600 border-red-500';
|
||||||
|
case 'warning':
|
||||||
|
return 'bg-yellow-600 border-yellow-500';
|
||||||
|
case 'info':
|
||||||
|
default:
|
||||||
|
return 'bg-blue-600 border-blue-500';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-4 right-4 z-50 space-y-2 max-w-sm">
|
||||||
|
{notifications.map((notification) => (
|
||||||
|
<div
|
||||||
|
key={notification.id}
|
||||||
|
className={`${getColors(notification.type)} border-l-4 rounded-lg shadow-2xl p-4 text-white animate-slide-in-right`}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="flex-shrink-0 mt-0.5">
|
||||||
|
{getIcon(notification.type)}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h4 className="font-semibold text-sm mb-1">{notification.title}</h4>
|
||||||
|
<p className="text-sm opacity-90">{notification.message}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => removeNotification(notification.id)}
|
||||||
|
className="flex-shrink-0 hover:bg-white hover:bg-opacity-20 rounded p-1 transition"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вспомогательная функция для отправки уведомлений
|
||||||
|
export const notify = (type, title, message) => {
|
||||||
|
window.dispatchEvent(new CustomEvent('notification', {
|
||||||
|
detail: { type, title, message }
|
||||||
|
}));
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { User, Lock, Server, MessageSquare, Shield, TrendingUp, Eye, EyeOff } from 'lucide-react';
|
import { User, Lock, Server, MessageSquare, Shield, TrendingUp, Eye, EyeOff } from 'lucide-react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { API_URL } from '../config';
|
import { API_URL } from '../config';
|
||||||
|
import { notify } from './NotificationSystem';
|
||||||
|
|
||||||
export default function Profile({ token, user, theme, onUsernameChange, viewingUsername }) {
|
export default function Profile({ token, user, theme, onUsernameChange, viewingUsername }) {
|
||||||
const [stats, setStats] = useState(null);
|
const [stats, setStats] = useState(null);
|
||||||
@@ -66,6 +67,7 @@ export default function Profile({ token, user, theme, onUsernameChange, viewingU
|
|||||||
// Обновляем токен
|
// Обновляем токен
|
||||||
localStorage.setItem('token', data.access_token);
|
localStorage.setItem('token', data.access_token);
|
||||||
|
|
||||||
|
notify('success', 'Имя изменено', `Ваше новое имя: ${data.username}`);
|
||||||
alert('Имя пользователя успешно изменено!');
|
alert('Имя пользователя успешно изменено!');
|
||||||
setUsernameForm({ new_username: '', password: '' });
|
setUsernameForm({ new_username: '', password: '' });
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ export default function Profile({ token, user, theme, onUsernameChange, viewingU
|
|||||||
|
|
||||||
loadStats();
|
loadStats();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify('error', 'Ошибка изменения', error.response?.data?.detail || 'Не удалось изменить имя');
|
||||||
alert(error.response?.data?.detail || 'Ошибка изменения имени пользователя');
|
alert(error.response?.data?.detail || 'Ошибка изменения имени пользователя');
|
||||||
} finally {
|
} finally {
|
||||||
setUsernameLoading(false);
|
setUsernameLoading(false);
|
||||||
@@ -111,9 +114,11 @@ export default function Profile({ token, user, theme, onUsernameChange, viewingU
|
|||||||
{ headers: { Authorization: `Bearer ${token}` } }
|
{ headers: { Authorization: `Bearer ${token}` } }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
notify('success', 'Пароль изменён', 'Ваш пароль успешно обновлен');
|
||||||
alert('Пароль успешно изменён!');
|
alert('Пароль успешно изменён!');
|
||||||
setPasswordForm({ old_password: '', new_password: '', confirm_password: '' });
|
setPasswordForm({ old_password: '', new_password: '', confirm_password: '' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notify('error', 'Ошибка изменения', error.response?.data?.detail || 'Не удалось изменить пароль');
|
||||||
alert(error.response?.data?.detail || 'Ошибка изменения пароля');
|
alert(error.response?.data?.detail || 'Ошибка изменения пароля');
|
||||||
} finally {
|
} finally {
|
||||||
setPasswordLoading(false);
|
setPasswordLoading(false);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Cpu, HardDrive, Activity } from 'lucide-react';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { API_URL } from '../config';
|
import { API_URL } from '../config';
|
||||||
|
|
||||||
export default function Stats({ serverName, token }) {
|
export default function Stats({ serverName, token, theme }) {
|
||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
status: 'stopped',
|
status: 'stopped',
|
||||||
cpu: 0,
|
cpu: 0,
|
||||||
@@ -29,17 +29,17 @@ export default function Stats({ serverName, token }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-8 bg-gray-900">
|
<div className={`p-8 ${theme.primary}`}>
|
||||||
<h2 className="text-2xl font-bold mb-6">Статистика сервера</h2>
|
<h2 className={`text-2xl font-bold mb-6 ${theme.text}`}>Статистика сервера</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<div className="bg-gray-800 rounded-lg p-6 border border-gray-700">
|
<div className={`${theme.card} rounded-lg p-6 ${theme.border} border`}>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-semibold">CPU</h3>
|
<h3 className={`text-lg font-semibold ${theme.text}`}>CPU</h3>
|
||||||
<Cpu className="w-6 h-6 text-blue-400" />
|
<Cpu className="w-6 h-6 text-blue-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-bold mb-2">{stats.cpu}%</div>
|
<div className={`text-3xl font-bold mb-2 ${theme.text}`}>{stats.cpu}%</div>
|
||||||
<div className="w-full bg-gray-700 rounded-full h-2">
|
<div className={`w-full ${theme.tertiary} rounded-full h-2`}>
|
||||||
<div
|
<div
|
||||||
className="bg-blue-500 h-2 rounded-full transition-all"
|
className="bg-blue-500 h-2 rounded-full transition-all"
|
||||||
style={{ width: `${Math.min(stats.cpu, 100)}%` }}
|
style={{ width: `${Math.min(stats.cpu, 100)}%` }}
|
||||||
@@ -47,13 +47,13 @@ export default function Stats({ serverName, token }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-800 rounded-lg p-6 border border-gray-700">
|
<div className={`${theme.card} rounded-lg p-6 ${theme.border} border`}>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-semibold">ОЗУ</h3>
|
<h3 className={`text-lg font-semibold ${theme.text}`}>ОЗУ</h3>
|
||||||
<Activity className="w-6 h-6 text-green-400" />
|
<Activity className="w-6 h-6 text-green-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-bold mb-2">{stats.memory} МБ</div>
|
<div className={`text-3xl font-bold mb-2 ${theme.text}`}>{stats.memory} МБ</div>
|
||||||
<div className="w-full bg-gray-700 rounded-full h-2">
|
<div className={`w-full ${theme.tertiary} rounded-full h-2`}>
|
||||||
<div
|
<div
|
||||||
className="bg-green-500 h-2 rounded-full transition-all"
|
className="bg-green-500 h-2 rounded-full transition-all"
|
||||||
style={{ width: `${Math.min((stats.memory / 2048) * 100, 100)}%` }}
|
style={{ width: `${Math.min((stats.memory / 2048) * 100, 100)}%` }}
|
||||||
@@ -61,27 +61,27 @@ export default function Stats({ serverName, token }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-800 rounded-lg p-6 border border-gray-700">
|
<div className={`${theme.card} rounded-lg p-6 ${theme.border} border`}>
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-semibold">Диск</h3>
|
<h3 className={`text-lg font-semibold ${theme.text}`}>Диск</h3>
|
||||||
<HardDrive className="w-6 h-6 text-purple-400" />
|
<HardDrive className="w-6 h-6 text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-3xl font-bold mb-2">{stats.disk} МБ</div>
|
<div className={`text-3xl font-bold mb-2 ${theme.text}`}>{stats.disk} МБ</div>
|
||||||
<div className="text-sm text-gray-400 mt-2">
|
<div className={`text-sm ${theme.textSecondary} mt-2`}>
|
||||||
Использовано на диске
|
Использовано на диске
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 bg-gray-800 rounded-lg p-6 border border-gray-700">
|
<div className={`mt-8 ${theme.card} rounded-lg p-6 ${theme.border} border`}>
|
||||||
<h3 className="text-lg font-semibold mb-4">Статус</h3>
|
<h3 className={`text-lg font-semibold mb-4 ${theme.text}`}>Статус</h3>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div
|
<div
|
||||||
className={`w-4 h-4 rounded-full ${
|
className={`w-4 h-4 rounded-full ${
|
||||||
stats.status === 'running' ? 'bg-green-500' : 'bg-red-500'
|
stats.status === 'running' ? 'bg-green-500' : 'bg-red-500'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<span className="text-xl">
|
<span className={`text-xl ${theme.text}`}>
|
||||||
{stats.status === 'running' ? 'Запущен' : 'Остановлен'}
|
{stats.status === 'running' ? 'Запущен' : 'Остановлен'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export default function ThemeSelector({ currentTheme, onThemeChange }) {
|
|||||||
const theme = getTheme(currentTheme);
|
const theme = getTheme(currentTheme);
|
||||||
|
|
||||||
const themeColors = {
|
const themeColors = {
|
||||||
|
modern: 'bg-gradient-to-r from-green-600 to-emerald-600',
|
||||||
dark: 'bg-gray-800',
|
dark: 'bg-gray-800',
|
||||||
light: 'bg-gray-100',
|
light: 'bg-gray-100',
|
||||||
purple: 'bg-purple-600',
|
purple: 'bg-purple-600',
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import { useState, useEffect, useRef } from 'react';
|
|||||||
import { ArrowLeft, Send, Clock, AlertCircle, CheckCircle } from 'lucide-react';
|
import { ArrowLeft, Send, Clock, AlertCircle, CheckCircle } from 'lucide-react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { API_URL } from '../config';
|
import { API_URL } from '../config';
|
||||||
|
import { notify } from './NotificationSystem';
|
||||||
|
|
||||||
export default function TicketChat({ ticket, token, user, theme, onBack }) {
|
export default function TicketChat({ ticket, token, user, theme, onBack }) {
|
||||||
const [messages, setMessages] = useState(ticket.messages || []);
|
const [messages, setMessages] = useState(ticket.messages || []);
|
||||||
const [newMessage, setNewMessage] = useState('');
|
const [newMessage, setNewMessage] = useState('');
|
||||||
const [currentTicket, setCurrentTicket] = useState(ticket);
|
const [currentTicket, setCurrentTicket] = useState(ticket);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [previousMessagesCount, setPreviousMessagesCount] = useState(ticket.messages?.length || 0);
|
||||||
const messagesEndRef = useRef(null);
|
const messagesEndRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -29,6 +31,30 @@ export default function TicketChat({ ticket, token, user, theme, onBack }) {
|
|||||||
const { data } = await axios.get(`${API_URL}/api/tickets/${ticket.id}`, {
|
const { data } = await axios.get(`${API_URL}/api/tickets/${ticket.id}`, {
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Проверяем новые сообщения
|
||||||
|
if (data.messages.length > previousMessagesCount) {
|
||||||
|
const newMessagesCount = data.messages.length - previousMessagesCount;
|
||||||
|
const lastMessage = data.messages[data.messages.length - 1];
|
||||||
|
|
||||||
|
// Уведомляем только если сообщение не от текущего пользователя
|
||||||
|
if (lastMessage.author !== user.username && lastMessage.author !== 'system') {
|
||||||
|
notify('info', 'Новое сообщение', `${lastMessage.author}: ${lastMessage.text.substring(0, 50)}${lastMessage.text.length > 50 ? '...' : ''}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreviousMessagesCount(data.messages.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем изменение статуса
|
||||||
|
if (data.status !== currentTicket.status) {
|
||||||
|
const statusNames = {
|
||||||
|
'pending': 'На рассмотрении',
|
||||||
|
'in_progress': 'В работе',
|
||||||
|
'closed': 'Закрыт'
|
||||||
|
};
|
||||||
|
notify('info', 'Статус изменён', `Тикет #${ticket.id}: ${statusNames[data.status]}`);
|
||||||
|
}
|
||||||
|
|
||||||
setCurrentTicket(data);
|
setCurrentTicket(data);
|
||||||
setMessages(data.messages || []);
|
setMessages(data.messages || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -49,9 +75,12 @@ export default function TicketChat({ ticket, token, user, theme, onBack }) {
|
|||||||
);
|
);
|
||||||
setMessages(data.ticket.messages);
|
setMessages(data.ticket.messages);
|
||||||
setCurrentTicket(data.ticket);
|
setCurrentTicket(data.ticket);
|
||||||
|
setPreviousMessagesCount(data.ticket.messages.length);
|
||||||
setNewMessage('');
|
setNewMessage('');
|
||||||
|
notify('success', 'Сообщение отправлено', 'Ваше сообщение успешно отправлено');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка отправки сообщения:', error);
|
console.error('Ошибка отправки сообщения:', error);
|
||||||
|
notify('error', 'Ошибка отправки', error.response?.data?.detail || 'Не удалось отправить сообщение');
|
||||||
alert(error.response?.data?.detail || 'Ошибка отправки сообщения');
|
alert(error.response?.data?.detail || 'Ошибка отправки сообщения');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -59,6 +88,12 @@ export default function TicketChat({ ticket, token, user, theme, onBack }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const changeStatus = async (newStatus) => {
|
const changeStatus = async (newStatus) => {
|
||||||
|
const statusNames = {
|
||||||
|
'pending': 'На рассмотрении',
|
||||||
|
'in_progress': 'В работе',
|
||||||
|
'closed': 'Закрыт'
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.put(
|
const { data } = await axios.put(
|
||||||
`${API_URL}/api/tickets/${ticket.id}/status`,
|
`${API_URL}/api/tickets/${ticket.id}/status`,
|
||||||
@@ -67,8 +102,11 @@ export default function TicketChat({ ticket, token, user, theme, onBack }) {
|
|||||||
);
|
);
|
||||||
setCurrentTicket(data.ticket);
|
setCurrentTicket(data.ticket);
|
||||||
setMessages(data.ticket.messages);
|
setMessages(data.ticket.messages);
|
||||||
|
setPreviousMessagesCount(data.ticket.messages.length);
|
||||||
|
notify('success', 'Статус изменён', `Тикет #${ticket.id} теперь: ${statusNames[newStatus]}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка изменения статуса:', error);
|
console.error('Ошибка изменения статуса:', error);
|
||||||
|
notify('error', 'Ошибка изменения статуса', error.response?.data?.detail || 'Не удалось изменить статус');
|
||||||
alert(error.response?.data?.detail || 'Ошибка изменения статуса');
|
alert(error.response?.data?.detail || 'Ошибка изменения статуса');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import axios from 'axios';
|
|||||||
import { API_URL } from '../config';
|
import { API_URL } from '../config';
|
||||||
import TicketChat from './TicketChat';
|
import TicketChat from './TicketChat';
|
||||||
import CreateTicketModal from './CreateTicketModal';
|
import CreateTicketModal from './CreateTicketModal';
|
||||||
|
import { notify } from './NotificationSystem';
|
||||||
|
|
||||||
export default function Tickets({ token, user, theme }) {
|
export default function Tickets({ token, user, theme }) {
|
||||||
const [tickets, setTickets] = useState([]);
|
const [tickets, setTickets] = useState([]);
|
||||||
const [selectedTicket, setSelectedTicket] = useState(null);
|
const [selectedTicket, setSelectedTicket] = useState(null);
|
||||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [previousTickets, setPreviousTickets] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadTickets();
|
loadTickets();
|
||||||
@@ -22,6 +24,22 @@ export default function Tickets({ token, user, theme }) {
|
|||||||
const { data } = await axios.get(`${API_URL}/api/tickets`, {
|
const { data } = await axios.get(`${API_URL}/api/tickets`, {
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Проверяем новые сообщения в тикетах
|
||||||
|
if (previousTickets.length > 0) {
|
||||||
|
data.forEach(ticket => {
|
||||||
|
const prevTicket = previousTickets.find(t => t.id === ticket.id);
|
||||||
|
if (prevTicket && ticket.messages.length > prevTicket.messages.length) {
|
||||||
|
const newMessage = ticket.messages[ticket.messages.length - 1];
|
||||||
|
// Уведомляем только если сообщение не от текущего пользователя
|
||||||
|
if (newMessage.author !== user.username) {
|
||||||
|
notify('info', 'Новое сообщение', `Тикет #${ticket.id}: ${newMessage.author} ответил`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreviousTickets(data);
|
||||||
setTickets(data);
|
setTickets(data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -32,6 +50,7 @@ export default function Tickets({ token, user, theme }) {
|
|||||||
|
|
||||||
const handleTicketCreated = () => {
|
const handleTicketCreated = () => {
|
||||||
setShowCreateModal(false);
|
setShowCreateModal(false);
|
||||||
|
notify('success', 'Тикет создан', 'Ваш тикет успешно создан');
|
||||||
loadTickets();
|
loadTickets();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,3 +20,19 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Анимация для уведомлений */
|
||||||
|
@keyframes slide-in-right {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in-right {
|
||||||
|
animation: slide-in-right 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,27 @@
|
|||||||
export const themes = {
|
export const themes = {
|
||||||
|
modern: {
|
||||||
|
name: 'Современная',
|
||||||
|
gradient: 'from-green-400 to-emerald-600',
|
||||||
|
primary: 'bg-[#0f1115]',
|
||||||
|
secondary: 'bg-[#1a1d24]',
|
||||||
|
tertiary: 'bg-[#23262e]',
|
||||||
|
accent: 'bg-green-600',
|
||||||
|
accentHover: 'hover:bg-green-700',
|
||||||
|
text: 'text-gray-100',
|
||||||
|
textSecondary: 'text-gray-400',
|
||||||
|
border: 'border-gray-800',
|
||||||
|
hover: 'hover:bg-[#23262e]',
|
||||||
|
input: 'bg-[#0f1115] border-gray-700',
|
||||||
|
card: 'bg-[#1a1d24]',
|
||||||
|
cardHover: 'hover:bg-[#23262e]',
|
||||||
|
success: 'bg-green-600',
|
||||||
|
successHover: 'hover:bg-green-700',
|
||||||
|
danger: 'bg-gray-700',
|
||||||
|
dangerHover: 'hover:bg-gray-600',
|
||||||
|
warning: 'bg-yellow-600',
|
||||||
|
console: 'bg-[#0f1115]',
|
||||||
|
consoleText: 'text-gray-300',
|
||||||
|
},
|
||||||
dark: {
|
dark: {
|
||||||
name: 'Тёмная',
|
name: 'Тёмная',
|
||||||
gradient: 'from-blue-400 to-purple-600',
|
gradient: 'from-blue-400 to-purple-600',
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
# ⚡ Быстрое исправление ошибки
|
|
||||||
|
|
||||||
## Проблема решена! ✅
|
|
||||||
|
|
||||||
Ошибка `ModuleNotFoundError: No module named 'authlib.integrations.fastapi_client'` исправлена.
|
|
||||||
|
|
||||||
## Что нужно сделать:
|
|
||||||
|
|
||||||
### 1. Установите зависимости (30 секунд)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
pip install authlib==1.3.0 httpx==0.26.0
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Запустите сервер (10 секунд)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✅ Должно работать!
|
|
||||||
|
|
||||||
Вы увидите:
|
|
||||||
```
|
|
||||||
⚠ ZITADEL провайдер не настроен. Проверьте .env файл.
|
|
||||||
INFO: Uvicorn running on http://0.0.0.0:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
Это нормально! Предупреждение исчезнет после настройки ZITADEL.
|
|
||||||
|
|
||||||
## 🚀 Что дальше?
|
|
||||||
|
|
||||||
1. **Настройте ZITADEL** → `ZITADEL_QUICK_START.md`
|
|
||||||
2. **Обновите .env** → Добавьте настройки ZITADEL
|
|
||||||
3. **Перезапустите** → `python main.py`
|
|
||||||
4. **Проверьте** → http://localhost:3000
|
|
||||||
|
|
||||||
## 📚 Подробнее
|
|
||||||
|
|
||||||
- **Полная инструкция:** `ИСПРАВЛЕНИЕ_ОШИБКИ.md`
|
|
||||||
- **Документация:** `README_OIDC.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Всё работает?** Отлично! Переходите к настройке ZITADEL! 🎉
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
# 🚀 Быстрый старт MC Panel
|
|
||||||
|
|
||||||
## Запуск панели
|
|
||||||
|
|
||||||
### 1️⃣ Запустите бэкенд
|
|
||||||
Откройте первый терминал:
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python main_new.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2️⃣ Запустите фронтенд
|
|
||||||
Откройте второй терминал:
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3️⃣ Откройте в браузере
|
|
||||||
```
|
|
||||||
http://localhost:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4️⃣ Войдите в систему
|
|
||||||
- **Логин**: `Sofa12345`
|
|
||||||
- **Пароль**: `arkonsad123`
|
|
||||||
|
|
||||||
## 🎨 Смена темы
|
|
||||||
|
|
||||||
1. Нажмите на иконку палитры (🎨) в правом верхнем углу
|
|
||||||
2. Выберите тему:
|
|
||||||
- 🌑 Тёмная
|
|
||||||
- ☀️ Светлая
|
|
||||||
- 💜 Фиолетовая
|
|
||||||
- 💙 Синяя
|
|
||||||
- 💚 Зелёная
|
|
||||||
|
|
||||||
## 📋 Основные функции
|
|
||||||
|
|
||||||
### Создание сервера
|
|
||||||
1. Нажмите кнопку "+" в левой панели
|
|
||||||
2. Заполните форму:
|
|
||||||
- Имя папки (латиница, цифры, _ и -)
|
|
||||||
- Отображаемое имя
|
|
||||||
- Команда запуска
|
|
||||||
3. Нажмите "Создать"
|
|
||||||
|
|
||||||
### Управление сервером
|
|
||||||
- **Запуск**: кнопка "Запустить" на карточке сервера
|
|
||||||
- **Остановка**: кнопка "Остановить" на карточке сервера
|
|
||||||
- **Консоль**: вкладка "Консоль" для просмотра логов
|
|
||||||
- **Файлы**: вкладка "Файлы" для управления файлами
|
|
||||||
- **Статистика**: вкладка "Статистика" для мониторинга ресурсов
|
|
||||||
- **Настройки**: вкладка "Настройки" для изменения параметров
|
|
||||||
|
|
||||||
### Управление пользователями (только для админов)
|
|
||||||
1. Нажмите кнопку "Пользователи" в header
|
|
||||||
2. Создайте новых пользователей
|
|
||||||
3. Выдайте доступ к серверам
|
|
||||||
4. Назначьте роли (Пользователь, Тех. поддержка, Администратор)
|
|
||||||
|
|
||||||
### Система тикетов 🎫
|
|
||||||
1. Нажмите кнопку "Тикеты" в header
|
|
||||||
2. Создайте тикет с описанием проблемы
|
|
||||||
3. Общайтесь в чате тикета
|
|
||||||
4. Тех. поддержка и админы могут менять статусы:
|
|
||||||
- 🟡 На рассмотрении
|
|
||||||
- 🔵 В работе
|
|
||||||
- 🟢 Закрыт
|
|
||||||
|
|
||||||
### Выдача доступа к серверу
|
|
||||||
1. Выберите сервер
|
|
||||||
2. Перейдите в "Настройки"
|
|
||||||
3. В разделе "Управление доступом" выберите пользователя
|
|
||||||
4. Нажмите "Выдать доступ"
|
|
||||||
|
|
||||||
## 🌐 Доступ через сеть
|
|
||||||
|
|
||||||
Панель автоматически определяет ваш IP-адрес и работает через:
|
|
||||||
- Локальную сеть
|
|
||||||
- Radmin VPN
|
|
||||||
- Другие VPN-сети
|
|
||||||
|
|
||||||
Друзья могут подключиться по вашему IP-адресу на порту 3000.
|
|
||||||
|
|
||||||
## ✅ Готово!
|
|
||||||
|
|
||||||
Панель готова к использованию. Создавайте серверы, управляйте ими и наслаждайтесь современным интерфейсом! 🎉
|
|
||||||
334
ГОТОВО.md
334
ГОТОВО.md
@@ -1,181 +1,225 @@
|
|||||||
# ✅ MC Panel готова к использованию!
|
# ✅ Документация объединена!
|
||||||
|
|
||||||
## 🎉 Что сделано
|
## Что было сделано
|
||||||
|
|
||||||
### 1. Изменён логин администратора
|
### 📚 Создано 3 основных файла:
|
||||||
- **Старый**: admin / admin
|
|
||||||
- **Новый**: Sofa12345 / arkonsad123
|
|
||||||
|
|
||||||
### 2. Добавлена система тикетов 🎫
|
#### 1. **README.md** - Главная страница
|
||||||
- Кнопка "Тикеты" в header
|
Навигация по всей документации с кратким описанием проекта.
|
||||||
- Создание тикетов с темой и описанием
|
|
||||||
- Чат для общения в тикете
|
|
||||||
- Три статуса:
|
|
||||||
- 🟡 На рассмотрении
|
|
||||||
- 🔵 В работе
|
|
||||||
- 🟢 Закрыт
|
|
||||||
|
|
||||||
### 3. Добавлена роль "Тех. поддержка" 👨💻
|
**Содержит:**
|
||||||
- Доступ ко всем тикетам
|
- Ссылки на всю документацию
|
||||||
- Возможность менять статусы
|
- Быстрый старт
|
||||||
- Возможность отвечать на тикеты
|
- Основные возможности
|
||||||
- Отдельный бейдж в интерфейсе
|
- Структура проекта
|
||||||
|
- Информация о поддержке
|
||||||
|
|
||||||
### 4. Добавлен личный кабинет 👤
|
**Начните отсюда!** 👈
|
||||||
- Кнопка "Личный кабинет" в header рядом с "Тикеты"
|
|
||||||
- Три вкладки:
|
|
||||||
- 📊 Обзор - статистика профиля
|
|
||||||
- 👤 Имя пользователя - изменение имени
|
|
||||||
- 🔒 Пароль - изменение пароля
|
|
||||||
- Статистика по серверам и тикетам
|
|
||||||
- Список своих серверов
|
|
||||||
|
|
||||||
### 5. Улучшено управление пользователями
|
---
|
||||||
- Выпадающий список для выбора роли
|
|
||||||
- Три роли: Пользователь, Тех. поддержка, Администратор
|
|
||||||
- Цветные индикаторы ролей
|
|
||||||
|
|
||||||
## 🚀 Запуск панели
|
#### 2. **ДОКУМЕНТАЦИЯ.md** - Полная документация проекта
|
||||||
|
Вся документация проекта в одном файле (кроме API).
|
||||||
|
|
||||||
### Шаг 1: Запустите бэкенд
|
**Разделы:**
|
||||||
```bash
|
1. О проекте
|
||||||
cd backend
|
2. Быстрый старт
|
||||||
python main.py
|
3. Установка и настройка
|
||||||
```
|
4. Функциональность
|
||||||
|
5. Система уведомлений
|
||||||
|
6. Дизайн и темы
|
||||||
|
7. Файловый менеджер
|
||||||
|
8. Система тикетов
|
||||||
|
9. Личный кабинет
|
||||||
|
10. OpenID Connect
|
||||||
|
11. Роли пользователей
|
||||||
|
12. Безопасность
|
||||||
|
13. Troubleshooting
|
||||||
|
14. Дополнительная информация
|
||||||
|
15. Changelog
|
||||||
|
|
||||||
### Шаг 2: Запустите фронтенд
|
**Объем:** ~500 строк
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Шаг 3: Откройте в браузере
|
---
|
||||||
```
|
|
||||||
http://localhost:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
### Шаг 4: Войдите как администратор
|
#### 3. **API.md** - API документация
|
||||||
- **Логин**: none
|
Вся API документация в одном файле.
|
||||||
- **Пароль**: none
|
|
||||||
|
|
||||||
## 📋 Быстрый старт
|
**Разделы:**
|
||||||
|
1. Базовая информация
|
||||||
|
2. Быстрый старт
|
||||||
|
3. Аутентификация (3 эндпоинта)
|
||||||
|
4. Управление пользователями (4 эндпоинта)
|
||||||
|
5. Личный кабинет (4 эндпоинта)
|
||||||
|
6. Управление серверами (10 эндпоинтов)
|
||||||
|
7. Управление файлами (9 эндпоинтов)
|
||||||
|
8. Тикеты (5 эндпоинтов)
|
||||||
|
9. OpenID Connect (3 эндпоинта)
|
||||||
|
10. Коды ошибок
|
||||||
|
11. Примеры интеграции (Python, JavaScript, cURL)
|
||||||
|
12. Postman коллекция
|
||||||
|
|
||||||
### Создание пользователя тех. поддержки
|
**Всего эндпоинтов:** 37
|
||||||
1. Зарегистрируйте нового пользователя
|
**Объем:** ~300 строк
|
||||||
2. Войдите как админ (Sofa12345)
|
|
||||||
3. Нажмите "Пользователи"
|
|
||||||
4. Найдите нового пользователя
|
|
||||||
5. В выпадающем списке выберите "Тех. поддержка"
|
|
||||||
|
|
||||||
### Создание тикета
|
---
|
||||||
1. Войдите как обычный пользователь
|
|
||||||
2. Нажмите кнопку "Тикеты" в header
|
|
||||||
3. Нажмите "Создать тикет"
|
|
||||||
4. Заполните тему и описание
|
|
||||||
5. Нажмите "Создать"
|
|
||||||
|
|
||||||
### Работа с тикетом (тех. поддержка)
|
### 🗑️ Удалено 8 старых файлов:
|
||||||
1. Войдите как пользователь с ролью "Тех. поддержка"
|
|
||||||
2. Нажмите "Тикеты"
|
|
||||||
3. Выберите тикет из списка
|
|
||||||
4. Отвечайте на сообщения
|
|
||||||
5. Меняйте статус тикета кнопками вверху
|
|
||||||
|
|
||||||
## 🎨 Возможности
|
- ❌ API_README.md
|
||||||
|
- ❌ API_QUICK_REFERENCE.md
|
||||||
|
- ❌ API_ДОКУМЕНТАЦИЯ.md
|
||||||
|
- ❌ API_DOCUMENTATION.md
|
||||||
|
- ❌ ДОКУМЕНТАЦИЯ_ГОТОВА.md
|
||||||
|
- ❌ СИСТЕМА_УВЕДОМЛЕНИЙ.md
|
||||||
|
- ❌ УВЕДОМЛЕНИЯ_ТИКЕТОВ.md
|
||||||
|
- ❌ ОБНОВЛЕНИЕ_УВЕДОМЛЕНИЙ.md
|
||||||
|
|
||||||
### Для всех пользователей
|
---
|
||||||
- ✅ Создание серверов
|
|
||||||
- ✅ Управление своими серверами
|
|
||||||
- ✅ Создание тикетов
|
|
||||||
- ✅ Общение в своих тикетах
|
|
||||||
- ✅ Смена темы интерфейса
|
|
||||||
- ✅ Личный кабинет с статистикой
|
|
||||||
- ✅ Изменение имени пользователя
|
|
||||||
- ✅ Изменение пароля
|
|
||||||
|
|
||||||
### Для тех. поддержки
|
### 📦 Сохранено:
|
||||||
- ✅ Все возможности пользователя
|
|
||||||
- ✅ Просмотр всех тикетов
|
|
||||||
- ✅ Ответы на любые тикеты
|
|
||||||
- ✅ Изменение статусов тикетов
|
|
||||||
|
|
||||||
### Для администраторов
|
- ✅ **MC_Panel_API.postman_collection.json** - Postman коллекция
|
||||||
- ✅ Все возможности тех. поддержки
|
- ✅ Все остальные технические .md файлы (история разработки)
|
||||||
- ✅ Управление пользователями
|
|
||||||
- ✅ Назначение ролей
|
|
||||||
- ✅ Удаление пользователей
|
|
||||||
- ✅ Управление доступом к серверам
|
|
||||||
|
|
||||||
## 📁 Структура проекта
|
---
|
||||||
|
|
||||||
|
## 📊 Статистика
|
||||||
|
|
||||||
|
### Было:
|
||||||
|
- 📄 12+ разрозненных .md файлов
|
||||||
|
- 🔀 Дублирование информации
|
||||||
|
- 😕 Сложная навигация
|
||||||
|
|
||||||
|
### Стало:
|
||||||
|
- 📄 3 основных файла
|
||||||
|
- ✨ Вся информация структурирована
|
||||||
|
- 🎯 Простая навигация
|
||||||
|
- 📖 Легко найти нужное
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Структура документации
|
||||||
|
|
||||||
```
|
```
|
||||||
MC Panel/
|
MC Panel/
|
||||||
├── backend/
|
├── README.md # 👈 Начните здесь!
|
||||||
│ ├── main.py # Основной файл бэкенда
|
│ ├── Навигация
|
||||||
│ ├── users.json # База пользователей
|
│ ├── Быстрый старт
|
||||||
│ ├── tickets.json # База тикетов (создаётся автоматически)
|
│ └── Ссылки на документацию
|
||||||
│ └── servers/ # Папка с серверами
|
|
||||||
│
|
│
|
||||||
├── frontend/
|
├── ДОКУМЕНТАЦИЯ.md # Полная документация
|
||||||
│ └── src/
|
│ ├── О проекте
|
||||||
│ ├── App.jsx # Главный компонент
|
│ ├── Установка
|
||||||
│ ├── themes.js # Конфигурация тем
|
│ ├── Функциональность
|
||||||
│ └── components/
|
│ ├── Система уведомлений
|
||||||
│ ├── Auth.jsx # Авторизация
|
│ ├── Файловый менеджер
|
||||||
│ ├── Profile.jsx # Личный кабинет
|
│ ├── Тикеты
|
||||||
│ ├── Tickets.jsx # Список тикетов
|
│ ├── Личный кабинет
|
||||||
│ ├── TicketChat.jsx # Чат тикета
|
│ ├── OpenID Connect
|
||||||
│ ├── CreateTicketModal.jsx # Создание тикета
|
│ ├── Роли
|
||||||
│ ├── Users.jsx # Управление пользователями
|
│ ├── Безопасность
|
||||||
│ ├── Console.jsx # Консоль сервера
|
│ └── Troubleshooting
|
||||||
│ ├── FileManager.jsx # Менеджер файлов
|
|
||||||
│ ├── Stats.jsx # Статистика
|
|
||||||
│ └── ServerSettings.jsx # Настройки сервера
|
|
||||||
│
|
│
|
||||||
└── Документация/
|
├── API.md # API документация
|
||||||
├── ГОТОВО.md # Этот файл
|
│ ├── Все эндпоинты (37)
|
||||||
├── PROFILE_SYSTEM.md # Документация личного кабинета
|
│ ├── Примеры запросов
|
||||||
├── TICKETS_SYSTEM.md # Документация системы тикетов
|
│ ├── Коды ошибок
|
||||||
├── CHANGELOG.md # История изменений
|
│ └── Интеграция (Python, JS, cURL)
|
||||||
└── БЫСТРЫЙ_СТАРТ.md # Быстрый старт
|
│
|
||||||
|
└── MC_Panel_API.postman_collection.json # Postman коллекция
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🎯 Что дальше?
|
---
|
||||||
|
|
||||||
### Тестирование
|
## 🚀 Как использовать
|
||||||
1. Создайте несколько пользователей
|
|
||||||
2. Назначьте одному роль "Тех. поддержка"
|
|
||||||
3. Создайте тикет от имени обычного пользователя
|
|
||||||
4. Ответьте на тикет от имени тех. поддержки
|
|
||||||
5. Измените статус тикета
|
|
||||||
|
|
||||||
### Настройка
|
### Для пользователей:
|
||||||
1. Измените темы под свой вкус в `frontend/src/themes.js`
|
1. Откройте **README.md** для обзора
|
||||||
2. Настройте порты в конфигурации
|
2. Читайте **ДОКУМЕНТАЦИЯ.md** для изучения функций
|
||||||
3. Добавьте свои серверы
|
3. Используйте **Troubleshooting** при проблемах
|
||||||
|
|
||||||
### Развёртывание
|
### Для разработчиков:
|
||||||
1. Настройте production сборку фронтенда
|
1. Откройте **README.md** для обзора
|
||||||
2. Настройте HTTPS для безопасности
|
2. Читайте **API.md** для интеграции
|
||||||
3. Настройте базу данных вместо JSON файлов
|
3. Импортируйте **Postman коллекцию** для тестирования
|
||||||
4. Настройте резервное копирование
|
|
||||||
|
|
||||||
## 📞 Поддержка
|
### Для администраторов:
|
||||||
|
1. Читайте **ДОКУМЕНТАЦИЯ.md** → Установка и настройка
|
||||||
|
2. Изучите раздел **Безопасность**
|
||||||
|
3. Настройте **OpenID Connect**
|
||||||
|
|
||||||
Если возникли вопросы:
|
---
|
||||||
1. Прочитайте `TICKETS_SYSTEM.md`
|
|
||||||
2. Прочитайте `CHANGELOG.md`
|
|
||||||
3. Создайте тикет в системе
|
|
||||||
|
|
||||||
## ✨ Готово!
|
## ✨ Преимущества новой структуры
|
||||||
|
|
||||||
Панель MC Panel полностью готова к использованию со всеми функциями:
|
### 1. Простота
|
||||||
- ✅ Управление серверами
|
- Всего 3 файла вместо 12+
|
||||||
- ✅ Система пользователей
|
- Легко найти нужную информацию
|
||||||
- ✅ Система тикетов
|
- Понятная навигация
|
||||||
- ✅ Роль тех. поддержки
|
|
||||||
|
### 2. Полнота
|
||||||
|
- Вся информация в одном месте
|
||||||
|
- Нет дублирования
|
||||||
|
- Актуальные данные
|
||||||
|
|
||||||
|
### 3. Удобство
|
||||||
|
- README с навигацией
|
||||||
|
- Разделение проект/API
|
||||||
|
- Быстрый поиск (Ctrl+F)
|
||||||
|
|
||||||
|
### 4. Поддержка
|
||||||
|
- Легко обновлять
|
||||||
|
- Легко добавлять новое
|
||||||
|
- Легко переводить
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Что включено
|
||||||
|
|
||||||
|
### ДОКУМЕНТАЦИЯ.md содержит:
|
||||||
|
- ✅ Быстрый старт
|
||||||
|
- ✅ Полная установка
|
||||||
|
- ✅ Все функции проекта
|
||||||
|
- ✅ Система уведомлений (полное описание)
|
||||||
|
- ✅ Дизайн и темы (6 тем)
|
||||||
|
- ✅ Файловый менеджер (все операции)
|
||||||
|
- ✅ Система тикетов (с уведомлениями)
|
||||||
- ✅ Личный кабинет
|
- ✅ Личный кабинет
|
||||||
- ✅ 5 тем оформления
|
- ✅ OpenID Connect (ZITADEL)
|
||||||
- ✅ Современный интерфейс
|
- ✅ Роли пользователей (4 роли)
|
||||||
|
- ✅ Безопасность (рекомендации)
|
||||||
|
- ✅ Troubleshooting (решение проблем)
|
||||||
|
- ✅ Структура БД
|
||||||
|
- ✅ Горячие клавиши
|
||||||
|
- ✅ Советы и трюки
|
||||||
|
- ✅ Roadmap
|
||||||
|
- ✅ Changelog
|
||||||
|
|
||||||
**Наслаждайтесь использованием! 🚀**
|
### API.md содержит:
|
||||||
|
- ✅ Все 37 эндпоинтов
|
||||||
|
- ✅ Примеры запросов/ответов
|
||||||
|
- ✅ Коды ошибок
|
||||||
|
- ✅ Примеры интеграции:
|
||||||
|
- Python (класс MCPanelAPI)
|
||||||
|
- JavaScript (класс MCPanelAPI)
|
||||||
|
- cURL (готовые команды)
|
||||||
|
- ✅ Postman коллекция (описание)
|
||||||
|
- ✅ Безопасность API
|
||||||
|
- ✅ Лимиты и ограничения
|
||||||
|
- ✅ Changelog
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 Готово к использованию!
|
||||||
|
|
||||||
|
Вся документация объединена и структурирована.
|
||||||
|
|
||||||
|
**Начните с файла README.md** для навигации по документации.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Дата создания:** 15 января 2026
|
||||||
|
**Версия:** 1.0.0
|
||||||
|
|
||||||
|
**Приятного использования MC Panel!** 🎮
|
||||||
|
|||||||
139
ГОТОВО_OIDC.md
139
ГОТОВО_OIDC.md
@@ -1,139 +0,0 @@
|
|||||||
# ✅ OpenID Connect с ZITADEL - Готово!
|
|
||||||
|
|
||||||
## 🎉 Что сделано
|
|
||||||
|
|
||||||
Интеграция OpenID Connect с ZITADEL полностью завершена!
|
|
||||||
|
|
||||||
### Основные изменения
|
|
||||||
|
|
||||||
1. **Backend**
|
|
||||||
- ✅ Настроена интеграция с ZITADEL
|
|
||||||
- ✅ Автоматическое создание пользователей при первом входе
|
|
||||||
- ✅ Обработка OAuth callback
|
|
||||||
- ✅ Генерация JWT токенов
|
|
||||||
|
|
||||||
2. **Frontend**
|
|
||||||
- ✅ Кнопка "Войти через ZITADEL" 🔐
|
|
||||||
- ✅ Автоматический вход после OAuth
|
|
||||||
- ✅ Обработка токенов
|
|
||||||
|
|
||||||
3. **Документация**
|
|
||||||
- ✅ Полная инструкция по настройке
|
|
||||||
- ✅ Быстрый старт
|
|
||||||
- ✅ Решение проблем
|
|
||||||
|
|
||||||
## 🚀 Как начать использовать
|
|
||||||
|
|
||||||
### Шаг 1: Настройте ZITADEL
|
|
||||||
|
|
||||||
1. Зайдите на [zitadel.cloud](https://zitadel.cloud)
|
|
||||||
2. Создайте приложение (Web Application, Code with PKCE)
|
|
||||||
3. Добавьте Redirect URI: `http://localhost:8000/api/auth/oidc/zitadel/callback`
|
|
||||||
4. Сохраните Client ID и Client Secret
|
|
||||||
|
|
||||||
### Шаг 2: Настройте .env
|
|
||||||
|
|
||||||
Откройте `backend/.env` и добавьте:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ZITADEL_ISSUER=https://your-instance.zitadel.cloud
|
|
||||||
ZITADEL_CLIENT_ID=123456789012345678@your-project
|
|
||||||
ZITADEL_CLIENT_SECRET=your-secret-here
|
|
||||||
```
|
|
||||||
|
|
||||||
### Шаг 3: Запустите
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backend
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
|
|
||||||
# Frontend
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Шаг 4: Проверьте
|
|
||||||
|
|
||||||
Откройте http://localhost:3000 и увидите кнопку "Войти через ZITADEL"!
|
|
||||||
|
|
||||||
## 📚 Документация
|
|
||||||
|
|
||||||
- **`ZITADEL_QUICK_START.md`** - Быстрый старт (4 шага)
|
|
||||||
- **`OPENID_CONNECT_SETUP.md`** - Полная инструкция
|
|
||||||
- **`OIDC_CHANGES.md`** - Технические детали изменений
|
|
||||||
|
|
||||||
## 🔧 Файлы
|
|
||||||
|
|
||||||
### Изменённые
|
|
||||||
- `backend/main.py` - OAuth инициализация и endpoints
|
|
||||||
- `backend/oidc_config.py` - Конфигурация ZITADEL
|
|
||||||
- `backend/.env.example` - Пример настроек
|
|
||||||
- `frontend/src/components/Auth.jsx` - Кнопка ZITADEL
|
|
||||||
- `frontend/src/App.jsx` - Обработка callback
|
|
||||||
- `OPENID_CONNECT_SETUP.md` - Обновлённая документация
|
|
||||||
|
|
||||||
### Новые
|
|
||||||
- `ZITADEL_QUICK_START.md` - Быстрый старт
|
|
||||||
- `OIDC_CHANGES.md` - Резюме изменений
|
|
||||||
- `ГОТОВО_OIDC.md` - Этот файл
|
|
||||||
|
|
||||||
### Удалённые
|
|
||||||
- `frontend/src/components/AuthCallback.jsx` - Не используется
|
|
||||||
|
|
||||||
## ✨ Возможности
|
|
||||||
|
|
||||||
### Для пользователей
|
|
||||||
- 🔐 Вход через ZITADEL
|
|
||||||
- 👤 Автоматическое создание аккаунта
|
|
||||||
- 🔄 Обновление данных при каждом входе
|
|
||||||
- 🎨 Красивая кнопка входа
|
|
||||||
|
|
||||||
### Для администраторов
|
|
||||||
- 📊 Просмотр OIDC пользователей
|
|
||||||
- 🔧 Управление ролями
|
|
||||||
- 📝 История входов в логах
|
|
||||||
|
|
||||||
## 🎯 Что дальше?
|
|
||||||
|
|
||||||
Система готова к использованию! Можете:
|
|
||||||
|
|
||||||
1. **Протестировать** - Создайте тестового пользователя в ZITADEL
|
|
||||||
2. **Настроить продакшен** - Используйте HTTPS и настоящий домен
|
|
||||||
3. **Добавить функции** - Аватары, logout через ZITADEL, и т.д.
|
|
||||||
|
|
||||||
## 💡 Советы
|
|
||||||
|
|
||||||
### Для разработки
|
|
||||||
- Используйте ZITADEL Cloud (бесплатно)
|
|
||||||
- Тестируйте на localhost
|
|
||||||
- Проверяйте логи backend
|
|
||||||
|
|
||||||
### Для продакшена
|
|
||||||
- Используйте HTTPS
|
|
||||||
- Настройте правильные redirect URIs
|
|
||||||
- Регулярно обновляйте client secret
|
|
||||||
- Включите логирование
|
|
||||||
|
|
||||||
## 🆘 Проблемы?
|
|
||||||
|
|
||||||
### Кнопка не появляется
|
|
||||||
→ Проверьте `.env` файл и перезапустите backend
|
|
||||||
|
|
||||||
### Ошибка redirect_uri
|
|
||||||
→ Проверьте настройки в ZITADEL
|
|
||||||
|
|
||||||
### Ошибка invalid_client
|
|
||||||
→ Проверьте Client ID и Secret
|
|
||||||
|
|
||||||
**Подробнее в `ZITADEL_QUICK_START.md`**
|
|
||||||
|
|
||||||
## 🎊 Готово!
|
|
||||||
|
|
||||||
OpenID Connect с ZITADEL полностью настроен и работает!
|
|
||||||
|
|
||||||
Теперь пользователи могут входить через:
|
|
||||||
- ✅ Обычную форму (логин/пароль)
|
|
||||||
- ✅ ZITADEL (OpenID Connect)
|
|
||||||
|
|
||||||
**Приятного использования! 🚀**
|
|
||||||
998
ДОКУМЕНТАЦИЯ.md
Normal file
998
ДОКУМЕНТАЦИЯ.md
Normal file
@@ -0,0 +1,998 @@
|
|||||||
|
# MC Panel - Полная документация проекта
|
||||||
|
|
||||||
|
**Версия:** 1.0.0
|
||||||
|
**Дата:** 15 января 2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Содержание
|
||||||
|
|
||||||
|
1. [О проекте](#о-проекте)
|
||||||
|
2. [Быстрый старт](#быстрый-старт)
|
||||||
|
3. [Установка и настройка](#установка-и-настройка)
|
||||||
|
4. [Функциональность](#функциональность)
|
||||||
|
5. [Система уведомлений](#система-уведомлений)
|
||||||
|
6. [Дизайн и темы](#дизайн-и-темы)
|
||||||
|
7. [Файловый менеджер](#файловый-менеджер)
|
||||||
|
8. [Система тикетов](#система-тикетов)
|
||||||
|
9. [Личный кабинет](#личный-кабинет)
|
||||||
|
10. [OpenID Connect](#openid-connect)
|
||||||
|
11. [Роли пользователей](#роли-пользователей)
|
||||||
|
12. [Безопасность](#безопасность)
|
||||||
|
13. [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## О проекте
|
||||||
|
|
||||||
|
MC Panel - это современная веб-панель для управления Minecraft серверами с полным набором функций.
|
||||||
|
|
||||||
|
### Основные возможности
|
||||||
|
|
||||||
|
- 🖥️ **Управление серверами** - запуск, остановка, мониторинг
|
||||||
|
- 📁 **Файловый менеджер** - полное управление файлами сервера
|
||||||
|
- 💬 **Консоль** - отправка команд и просмотр логов в реальном времени
|
||||||
|
- 📊 **Статистика** - мониторинг CPU, RAM, диска
|
||||||
|
- 🎫 **Система тикетов** - поддержка пользователей
|
||||||
|
- 👥 **Управление пользователями** - роли и права доступа
|
||||||
|
- 🔐 **OpenID Connect** - интеграция с ZITADEL
|
||||||
|
- 🎨 **6 тем оформления** - включая современную темную тему
|
||||||
|
- 🔔 **Система уведомлений** - информирование о всех событиях
|
||||||
|
- 👤 **Личный кабинет** - управление профилем и статистика
|
||||||
|
|
||||||
|
### Технологии
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- FastAPI (Python)
|
||||||
|
- JWT аутентификация
|
||||||
|
- WebSocket для консоли
|
||||||
|
- Authlib для OpenID Connect
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- React 18
|
||||||
|
- Tailwind CSS
|
||||||
|
- Axios для API запросов
|
||||||
|
- Lucide React для иконок
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
### Шаг 1: Установка зависимостей
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Шаг 2: Настройка окружения
|
||||||
|
|
||||||
|
Создайте файл `.env` в корне проекта:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# ZITADEL OpenID Connect
|
||||||
|
ZITADEL_ISSUER=https://your-instance.zitadel.cloud
|
||||||
|
ZITADEL_CLIENT_ID=your_client_id
|
||||||
|
ZITADEL_CLIENT_SECRET=your_client_secret
|
||||||
|
|
||||||
|
# URLs
|
||||||
|
BASE_URL=http://localhost:8000
|
||||||
|
FRONTEND_URL=http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Шаг 3: Запуск
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
Сервер запустится на `http://localhost:8000`
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
Интерфейс будет доступен на `http://localhost:3000`
|
||||||
|
|
||||||
|
### Шаг 4: Первый вход
|
||||||
|
|
||||||
|
1. Откройте `http://localhost:3000`
|
||||||
|
2. Зарегистрируйтесь (первый пользователь получит роль admin)
|
||||||
|
3. Создайте свой первый сервер
|
||||||
|
4. Загрузите server.jar в папку сервера
|
||||||
|
5. Запустите сервер!
|
||||||
|
|
||||||
|
**Учетные данные по умолчанию:**
|
||||||
|
- Логин: `Root`
|
||||||
|
- Пароль: `Admin`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Node.js 16+
|
||||||
|
- npm или yarn
|
||||||
|
- Git
|
||||||
|
|
||||||
|
### Полная установка
|
||||||
|
|
||||||
|
#### 1. Клонирование репозитория
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd mc-panel
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Установка Backend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Зависимости:**
|
||||||
|
- fastapi - веб-фреймворк
|
||||||
|
- uvicorn - ASGI сервер
|
||||||
|
- python-jose - JWT токены
|
||||||
|
- passlib - хеширование паролей
|
||||||
|
- python-multipart - загрузка файлов
|
||||||
|
- psutil - мониторинг системы
|
||||||
|
- authlib - OpenID Connect
|
||||||
|
- httpx - HTTP клиент
|
||||||
|
- python-dotenv - переменные окружения
|
||||||
|
|
||||||
|
#### 3. Установка Frontend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
**Зависимости:**
|
||||||
|
- react - UI библиотека
|
||||||
|
- react-dom - рендеринг React
|
||||||
|
- axios - HTTP клиент
|
||||||
|
- lucide-react - иконки
|
||||||
|
- tailwindcss - CSS фреймворк
|
||||||
|
|
||||||
|
#### 4. Настройка ZITADEL (опционально)
|
||||||
|
|
||||||
|
Если хотите использовать OpenID Connect:
|
||||||
|
|
||||||
|
1. Создайте аккаунт на [ZITADEL](https://zitadel.com)
|
||||||
|
2. Создайте новое приложение (Application)
|
||||||
|
3. Выберите тип "Web"
|
||||||
|
4. Настройте Redirect URIs:
|
||||||
|
- `http://localhost:8000/api/auth/oidc/zitadel/callback`
|
||||||
|
5. Скопируйте Client ID и Client Secret
|
||||||
|
6. Добавьте в `.env` файл
|
||||||
|
|
||||||
|
#### 5. Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
mc-panel/
|
||||||
|
├── backend/
|
||||||
|
│ ├── main.py # Главный файл FastAPI
|
||||||
|
│ ├── oidc_config.py # Конфигурация OpenID Connect
|
||||||
|
│ ├── requirements.txt # Python зависимости
|
||||||
|
│ ├── users.json # База пользователей
|
||||||
|
│ ├── tickets.json # База тикетов
|
||||||
|
│ └── servers/ # Папка с серверами
|
||||||
|
├── frontend/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── App.jsx # Главный компонент
|
||||||
|
│ │ ├── components/ # React компоненты
|
||||||
|
│ │ ├── themes.js # Темы оформления
|
||||||
|
│ │ └── config.js # Конфигурация
|
||||||
|
│ ├── package.json # npm зависимости
|
||||||
|
│ └── vite.config.js # Конфигурация Vite
|
||||||
|
├── .env # Переменные окружения
|
||||||
|
└── ДОКУМЕНТАЦИЯ.md # Этот файл
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Функциональность
|
||||||
|
|
||||||
|
### Управление серверами
|
||||||
|
|
||||||
|
#### Создание сервера
|
||||||
|
1. Нажмите кнопку "+" в боковой панели
|
||||||
|
2. Заполните форму:
|
||||||
|
- **Имя папки** - только латиница, цифры, _ и -
|
||||||
|
- **Отображаемое имя** - любое название
|
||||||
|
- **Команда запуска** - команда для запуска сервера
|
||||||
|
3. Нажмите "Создать"
|
||||||
|
|
||||||
|
#### Запуск и остановка
|
||||||
|
- **Запустить** - зеленая кнопка "Запустить"
|
||||||
|
- **Остановить** - серая кнопка "Сброс"
|
||||||
|
- Статус отображается цветным индикатором
|
||||||
|
|
||||||
|
#### Консоль
|
||||||
|
- Просмотр логов в реальном времени
|
||||||
|
- Отправка команд серверу
|
||||||
|
- Цветная подсветка:
|
||||||
|
- 🟢 INFO - зеленый
|
||||||
|
- 🟡 WARN - желтый
|
||||||
|
- 🔴 ERROR - красный
|
||||||
|
- ⚪ Время - серый
|
||||||
|
|
||||||
|
#### Статистика
|
||||||
|
- **CPU** - использование процессора (%)
|
||||||
|
- **RAM** - использование памяти (МБ)
|
||||||
|
- **Disk** - размер файлов сервера (МБ)
|
||||||
|
- Обновление каждые 5 секунд
|
||||||
|
|
||||||
|
#### Настройки
|
||||||
|
- Изменение отображаемого имени
|
||||||
|
- Изменение команды запуска
|
||||||
|
- Удаление сервера (только админ)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Система уведомлений
|
||||||
|
|
||||||
|
### Описание
|
||||||
|
Полноценная система уведомлений с автоматическим исчезновением через 5 секунд.
|
||||||
|
|
||||||
|
### Типы уведомлений
|
||||||
|
- 🟢 **Success** - успешные операции
|
||||||
|
- 🔴 **Error** - ошибки
|
||||||
|
- 🟡 **Warning** - предупреждения
|
||||||
|
- 🔵 **Info** - информационные сообщения
|
||||||
|
|
||||||
|
### Где используются
|
||||||
|
|
||||||
|
#### Управление серверами
|
||||||
|
- ✅ Сервер запущен
|
||||||
|
- ℹ️ Сервер остановлен
|
||||||
|
- ❌ Ошибки запуска/остановки
|
||||||
|
|
||||||
|
#### Файловый менеджер
|
||||||
|
- ✅ Файл/папка создан(а)
|
||||||
|
- ✅ Файл загружен/удален/сохранен
|
||||||
|
- ✅ Файл переименован/перемещен
|
||||||
|
- ❌ Ошибки операций
|
||||||
|
|
||||||
|
#### Тикеты
|
||||||
|
- ✅ Тикет создан
|
||||||
|
- ✅ Сообщение отправлено
|
||||||
|
- ℹ️ Новое сообщение (от других, каждые 3 сек)
|
||||||
|
- ✅ Статус изменён (действие)
|
||||||
|
- ℹ️ Статус изменён (просмотр)
|
||||||
|
- ❌ Ошибки
|
||||||
|
|
||||||
|
**Особенности тикетов:**
|
||||||
|
- Автообновление каждые 3 секунды
|
||||||
|
- Превью сообщений (50 символов)
|
||||||
|
- Не показываются для собственных действий
|
||||||
|
|
||||||
|
#### Личный кабинет
|
||||||
|
- ✅ Имя изменено
|
||||||
|
- ✅ Пароль изменён
|
||||||
|
- ❌ Ошибки
|
||||||
|
|
||||||
|
#### Создание сервера
|
||||||
|
- ✅ Сервер создан
|
||||||
|
- ❌ Ошибка создания
|
||||||
|
|
||||||
|
### Технические детали
|
||||||
|
|
||||||
|
**Компонент:** `frontend/src/components/NotificationSystem.jsx`
|
||||||
|
|
||||||
|
**Использование:**
|
||||||
|
```javascript
|
||||||
|
import { notify } from './components/NotificationSystem';
|
||||||
|
|
||||||
|
notify('success', 'Заголовок', 'Сообщение');
|
||||||
|
notify('error', 'Ошибка', 'Описание');
|
||||||
|
notify('warning', 'Внимание', 'Предупреждение');
|
||||||
|
notify('info', 'Информация', 'Сообщение');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Анимация:** slide-in-right (0.3 сек)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Дизайн и темы
|
||||||
|
|
||||||
|
### Доступные темы
|
||||||
|
|
||||||
|
1. **Modern (Современная)** - по умолчанию
|
||||||
|
- Цвета: #0f1115, #1a1d24, #23262e
|
||||||
|
- Акцент: зеленый
|
||||||
|
- Градиент: зеленый → изумрудный
|
||||||
|
|
||||||
|
2. **Dark (Тёмная)**
|
||||||
|
- Цвета: черный, темно-серый
|
||||||
|
- Акцент: синий
|
||||||
|
- Градиент: синий → фиолетовый
|
||||||
|
|
||||||
|
3. **Light (Светлая)**
|
||||||
|
- Цвета: белый, светло-серый
|
||||||
|
- Акцент: синий
|
||||||
|
- Градиент: синий → фиолетовый
|
||||||
|
|
||||||
|
4. **Purple (Фиолетовая)**
|
||||||
|
- Цвета: темный с фиолетовым оттенком
|
||||||
|
- Акцент: фиолетовый
|
||||||
|
- Градиент: фиолетовый → розовый
|
||||||
|
|
||||||
|
5. **Blue (Синяя)**
|
||||||
|
- Цвета: темный с синим оттенком
|
||||||
|
- Акцент: синий
|
||||||
|
- Градиент: голубой → синий
|
||||||
|
|
||||||
|
6. **Green (Зелёная)**
|
||||||
|
- Цвета: темный с зеленым оттенком
|
||||||
|
- Акцент: зеленый
|
||||||
|
- Градиент: изумрудный → зеленый
|
||||||
|
|
||||||
|
### Переключение темы
|
||||||
|
Кнопка в правом верхнем углу → выбор темы из списка
|
||||||
|
|
||||||
|
### Особенности дизайна
|
||||||
|
- Цветная консоль (INFO, WARN, ERROR)
|
||||||
|
- Кнопки с тенями и hover эффектами
|
||||||
|
- Плавные переходы и анимации
|
||||||
|
- Адаптивный дизайн
|
||||||
|
- Современные иконки (Lucide React)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Файловый менеджер
|
||||||
|
|
||||||
|
### Возможности
|
||||||
|
|
||||||
|
#### Просмотр файлов
|
||||||
|
- Список файлов и папок
|
||||||
|
- 6 колонок: Имя, Тип, Размер, Изменение, Разрешение, Действия
|
||||||
|
- Поиск по названию
|
||||||
|
- Навигация по папкам
|
||||||
|
|
||||||
|
#### Создание
|
||||||
|
- **Файл** - кнопка "Новый" → "Создать файл"
|
||||||
|
- **Папка** - кнопка "Новый" → "Создать папку"
|
||||||
|
- Ввод имени и Enter
|
||||||
|
|
||||||
|
#### Загрузка и скачивание
|
||||||
|
- **Загрузить** - зеленая кнопка, выбор файла
|
||||||
|
- **Скачать** - иконка скачивания у файла
|
||||||
|
|
||||||
|
#### Редактирование
|
||||||
|
- **Просмотр** - иконка глаза
|
||||||
|
- **Редактирование** - иконка карандаша
|
||||||
|
- Сохранение изменений
|
||||||
|
|
||||||
|
#### Переименование
|
||||||
|
- Двойной клик по имени файла
|
||||||
|
- Ввод нового имени
|
||||||
|
- Enter для сохранения
|
||||||
|
|
||||||
|
#### Перемещение файлов
|
||||||
|
|
||||||
|
**Cut/Paste (Вырезать/Вставить):**
|
||||||
|
1. Выберите файлы чекбоксами
|
||||||
|
2. Нажмите "Вырезать" (оранжевая кнопка)
|
||||||
|
3. Перейдите в нужную папку
|
||||||
|
4. Нажмите "Вставить" (фиолетовая кнопка)
|
||||||
|
|
||||||
|
**Особенности:**
|
||||||
|
- Файлы подсвечиваются оранжевым
|
||||||
|
- Счетчик вырезанных файлов
|
||||||
|
- Кнопка "Отмена" для отмены операции
|
||||||
|
- Drag & Drop отключен
|
||||||
|
|
||||||
|
#### Удаление
|
||||||
|
- Иконка корзины
|
||||||
|
- Подтверждение удаления
|
||||||
|
- Удаление файлов и папок
|
||||||
|
|
||||||
|
#### Выбор файлов
|
||||||
|
- Чекбокс в заголовке - выбрать все
|
||||||
|
- Чекбоксы у файлов - выбор отдельных
|
||||||
|
- Кнопка "Обновить" - обновить список
|
||||||
|
|
||||||
|
### Интерфейс
|
||||||
|
- Поиск с иконкой
|
||||||
|
- Кнопки: Загрузить (зеленая), Обновить (серая), Новый (синяя)
|
||||||
|
- Кнопки перемещения: Вырезать (оранжевая), Вставить (фиолетовая), Отмена (серая)
|
||||||
|
- Таблица с hover эффектами
|
||||||
|
- "No data" при пустой папке
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Система тикетов
|
||||||
|
|
||||||
|
### Создание тикета
|
||||||
|
1. Кнопка "Тикеты" в шапке
|
||||||
|
2. Кнопка "+" для создания
|
||||||
|
3. Заполните:
|
||||||
|
- **Заголовок** - краткое описание
|
||||||
|
- **Описание** - подробности проблемы
|
||||||
|
4. Нажмите "Создать"
|
||||||
|
|
||||||
|
### Статусы тикетов
|
||||||
|
- 🟡 **На рассмотрении** (pending) - новый тикет
|
||||||
|
- 🔵 **В работе** (in_progress) - тикет взят в работу
|
||||||
|
- 🟢 **Закрыт** (closed) - проблема решена
|
||||||
|
|
||||||
|
### Работа с тикетами
|
||||||
|
|
||||||
|
#### Просмотр
|
||||||
|
- Список всех тикетов
|
||||||
|
- Фильтр по статусу (цветные индикаторы)
|
||||||
|
- Информация: автор, дата, статус
|
||||||
|
|
||||||
|
#### Чат
|
||||||
|
- Отправка сообщений
|
||||||
|
- Просмотр истории
|
||||||
|
- Автообновление каждые 3 секунды
|
||||||
|
- Уведомления о новых сообщениях
|
||||||
|
|
||||||
|
#### Изменение статуса (админ/поддержка)
|
||||||
|
- Кнопки статусов в шапке тикета
|
||||||
|
- Автоматическое системное сообщение
|
||||||
|
- Уведомление всем участникам
|
||||||
|
|
||||||
|
### Уведомления в тикетах
|
||||||
|
|
||||||
|
**При отправке сообщения:**
|
||||||
|
- ✅ "Сообщение отправлено"
|
||||||
|
|
||||||
|
**При получении сообщения:**
|
||||||
|
- ℹ️ "Новое сообщение: {автор}: {превью}..."
|
||||||
|
- Только от других пользователей
|
||||||
|
- Превью 50 символов
|
||||||
|
|
||||||
|
**При изменении статуса:**
|
||||||
|
- ✅ "Статус изменён: Тикет #X теперь: {статус}" (действие)
|
||||||
|
- ℹ️ "Статус изменён: Тикет #X: {статус}" (просмотр)
|
||||||
|
|
||||||
|
**При ошибках:**
|
||||||
|
- ❌ "Ошибка отправки" / "Ошибка изменения статуса"
|
||||||
|
|
||||||
|
### Права доступа
|
||||||
|
- **Пользователи** - видят только свои тикеты
|
||||||
|
- **Админы** - видят все тикеты, могут менять статус
|
||||||
|
- **Поддержка** - видят все тикеты, могут менять статус
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Личный кабинет
|
||||||
|
|
||||||
|
### Доступ
|
||||||
|
Кнопка "Личный кабинет" в правом верхнем углу
|
||||||
|
|
||||||
|
### Разделы
|
||||||
|
|
||||||
|
#### Обзор
|
||||||
|
- Имя пользователя и роль
|
||||||
|
- Статистика:
|
||||||
|
- Мои серверы (созданные)
|
||||||
|
- Доступные серверы (общие)
|
||||||
|
- Тикеты (всего, по статусам)
|
||||||
|
- Общее количество серверов
|
||||||
|
|
||||||
|
#### Безопасность
|
||||||
|
|
||||||
|
**Изменение имени пользователя:**
|
||||||
|
1. Введите новое имя (минимум 3 символа)
|
||||||
|
2. Введите текущий пароль
|
||||||
|
3. Нажмите "Изменить имя"
|
||||||
|
4. Получите новый токен
|
||||||
|
|
||||||
|
**Изменение пароля:**
|
||||||
|
1. Введите старый пароль
|
||||||
|
2. Введите новый пароль (минимум 6 символов)
|
||||||
|
3. Подтвердите новый пароль
|
||||||
|
4. Нажмите "Изменить пароль"
|
||||||
|
|
||||||
|
**Показ/скрытие паролей:**
|
||||||
|
- Иконка глаза для переключения видимости
|
||||||
|
|
||||||
|
### Просмотр чужих профилей (админ/поддержка)
|
||||||
|
|
||||||
|
Админы и техподдержка могут просматривать профили других пользователей:
|
||||||
|
- Статистика пользователя
|
||||||
|
- Список серверов
|
||||||
|
- Тикеты пользователя
|
||||||
|
- Индикатор "Просмотр профиля: {username}"
|
||||||
|
|
||||||
|
**Доступ:**
|
||||||
|
1. Раздел "Пользователи"
|
||||||
|
2. Кнопка "Профиль" у пользователя
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenID Connect
|
||||||
|
|
||||||
|
### Поддерживаемые провайдеры
|
||||||
|
- **ZITADEL** - основной провайдер
|
||||||
|
|
||||||
|
### Настройка ZITADEL
|
||||||
|
|
||||||
|
#### 1. Создание приложения
|
||||||
|
1. Зарегистрируйтесь на [ZITADEL](https://zitadel.com)
|
||||||
|
2. Создайте новый проект
|
||||||
|
3. Добавьте приложение (Application)
|
||||||
|
4. Выберите тип "Web"
|
||||||
|
5. Настройте Redirect URIs:
|
||||||
|
```
|
||||||
|
http://localhost:8000/api/auth/oidc/zitadel/callback
|
||||||
|
https://your-domain.com/api/auth/oidc/zitadel/callback
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Получение учетных данных
|
||||||
|
1. Скопируйте **Client ID**
|
||||||
|
2. Создайте и скопируйте **Client Secret**
|
||||||
|
3. Скопируйте **Issuer URL** (например: `https://your-instance.zitadel.cloud`)
|
||||||
|
|
||||||
|
#### 3. Настройка .env
|
||||||
|
```env
|
||||||
|
ZITADEL_ISSUER=https://your-instance.zitadel.cloud
|
||||||
|
ZITADEL_CLIENT_ID=your_client_id_here
|
||||||
|
ZITADEL_CLIENT_SECRET=your_client_secret_here
|
||||||
|
BASE_URL=http://localhost:8000
|
||||||
|
FRONTEND_URL=http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Использование
|
||||||
|
|
||||||
|
#### Вход через ZITADEL
|
||||||
|
1. На странице входа нажмите кнопку "ZITADEL"
|
||||||
|
2. Авторизуйтесь на странице ZITADEL
|
||||||
|
3. Разрешите доступ приложению
|
||||||
|
4. Автоматическое перенаправление в панель
|
||||||
|
|
||||||
|
#### Создание пользователя
|
||||||
|
- При первом входе автоматически создается пользователь
|
||||||
|
- Имя пользователя берется из email (до @)
|
||||||
|
- Роль: `user` (обычный пользователь)
|
||||||
|
- Пароль не требуется (используется OIDC)
|
||||||
|
|
||||||
|
#### Связывание аккаунтов
|
||||||
|
- Каждый OIDC аккаунт уникален
|
||||||
|
- Повторный вход использует существующего пользователя
|
||||||
|
- ID хранится в формате: `zitadel:{sub}`
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- Токены обновляются автоматически
|
||||||
|
- Используется PKCE для защиты
|
||||||
|
- Все данные передаются через HTTPS (в production)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Роли пользователей
|
||||||
|
|
||||||
|
### Admin (Администратор)
|
||||||
|
**Полный доступ ко всем функциям:**
|
||||||
|
- ✅ Все серверы (создание, удаление, управление)
|
||||||
|
- ✅ Все тикеты (просмотр, изменение статуса)
|
||||||
|
- ✅ Управление пользователями (роли, доступ, удаление)
|
||||||
|
- ✅ Просмотр всех профилей
|
||||||
|
- ✅ Изменение настроек серверов
|
||||||
|
|
||||||
|
**Получение роли:**
|
||||||
|
- Первый зарегистрированный пользователь
|
||||||
|
- Назначение другим админом
|
||||||
|
|
||||||
|
### User (Пользователь)
|
||||||
|
**Стандартные права:**
|
||||||
|
- ✅ Свои серверы (создание, управление)
|
||||||
|
- ✅ Серверы с предоставленным доступом
|
||||||
|
- ✅ Свои тикеты (создание, просмотр)
|
||||||
|
- ✅ Свой профиль
|
||||||
|
- ❌ Управление другими пользователями
|
||||||
|
- ❌ Удаление серверов
|
||||||
|
- ❌ Просмотр чужих тикетов
|
||||||
|
|
||||||
|
### Support (Техподдержка)
|
||||||
|
**Права поддержки:**
|
||||||
|
- ✅ Все тикеты (просмотр, ответы, изменение статуса)
|
||||||
|
- ✅ Просмотр профилей пользователей
|
||||||
|
- ✅ Свои серверы
|
||||||
|
- ❌ Управление пользователями
|
||||||
|
- ❌ Удаление серверов
|
||||||
|
- ❌ Изменение ролей
|
||||||
|
|
||||||
|
### Banned (Заблокирован)
|
||||||
|
**Нет доступа:**
|
||||||
|
- ❌ Вход в систему запрещен
|
||||||
|
- ❌ API запросы отклоняются
|
||||||
|
- ❌ Все функции недоступны
|
||||||
|
|
||||||
|
### Изменение ролей
|
||||||
|
|
||||||
|
**Только админы могут:**
|
||||||
|
1. Раздел "Пользователи"
|
||||||
|
2. Выбрать пользователя
|
||||||
|
3. Кнопка "Изменить роль"
|
||||||
|
4. Выбрать новую роль
|
||||||
|
5. Подтвердить
|
||||||
|
|
||||||
|
**Ограничения:**
|
||||||
|
- Нельзя изменить свою роль
|
||||||
|
- Нельзя удалить себя
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
### JWT Токены
|
||||||
|
- **Алгоритм:** HS256
|
||||||
|
- **Срок действия:** 7 дней
|
||||||
|
- **Хранение:** localStorage (фронтенд)
|
||||||
|
- **Передача:** Bearer Token в заголовке Authorization
|
||||||
|
|
||||||
|
### Пароли
|
||||||
|
- **Хеширование:** bcrypt
|
||||||
|
- **Минимальная длина:** 6 символов
|
||||||
|
- **Проверка:** при каждом входе
|
||||||
|
- **Изменение:** требует старый пароль
|
||||||
|
|
||||||
|
### Файловая безопасность
|
||||||
|
- Все пути проверяются на выход за пределы папки сервера
|
||||||
|
- Запрещены операции с файлами вне `servers/`
|
||||||
|
- Проверка прав доступа к серверу
|
||||||
|
|
||||||
|
### API безопасность
|
||||||
|
- Все эндпоинты требуют авторизацию (кроме login/register)
|
||||||
|
- Проверка роли для админских функций
|
||||||
|
- Валидация входных данных
|
||||||
|
- Защита от SQL injection (используется JSON)
|
||||||
|
|
||||||
|
### Рекомендации для production
|
||||||
|
|
||||||
|
#### 1. Измените SECRET_KEY
|
||||||
|
В `backend/main.py`:
|
||||||
|
```python
|
||||||
|
SECRET_KEY = "your-very-long-random-secret-key-here"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Используйте HTTPS
|
||||||
|
```env
|
||||||
|
BASE_URL=https://your-domain.com
|
||||||
|
FRONTEND_URL=https://your-domain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Настройте CORS
|
||||||
|
В `backend/main.py`:
|
||||||
|
```python
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["https://your-domain.com"], # Конкретный домен
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Используйте базу данных
|
||||||
|
Замените `users.json` и `tickets.json` на PostgreSQL/MySQL
|
||||||
|
|
||||||
|
#### 5. Настройте firewall
|
||||||
|
- Ограничьте доступ к портам
|
||||||
|
- Разрешите только необходимые IP
|
||||||
|
|
||||||
|
#### 6. Регулярные обновления
|
||||||
|
```bash
|
||||||
|
pip install --upgrade -r requirements.txt
|
||||||
|
npm update
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7. Логирование
|
||||||
|
Включите подробное логирование для отслеживания действий
|
||||||
|
|
||||||
|
#### 8. Backup
|
||||||
|
Регулярно создавайте резервные копии:
|
||||||
|
- `users.json`
|
||||||
|
- `tickets.json`
|
||||||
|
- Папка `servers/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Проблемы с запуском
|
||||||
|
|
||||||
|
#### Backend не запускается
|
||||||
|
```bash
|
||||||
|
# Проверьте Python версию
|
||||||
|
python --version # Должно быть 3.8+
|
||||||
|
|
||||||
|
# Переустановите зависимости
|
||||||
|
pip install --upgrade -r requirements.txt
|
||||||
|
|
||||||
|
# Проверьте порт 8000
|
||||||
|
netstat -ano | findstr :8000
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Frontend не запускается
|
||||||
|
```bash
|
||||||
|
# Проверьте Node.js версию
|
||||||
|
node --version # Должно быть 16+
|
||||||
|
|
||||||
|
# Очистите кэш и переустановите
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Проверьте порт 3000
|
||||||
|
netstat -ano | findstr :3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проблемы с аутентификацией
|
||||||
|
|
||||||
|
#### "Неверный токен"
|
||||||
|
- Токен истек (7 дней)
|
||||||
|
- Измененный SECRET_KEY
|
||||||
|
- **Решение:** Выйдите и войдите снова
|
||||||
|
|
||||||
|
#### "Требуется авторизация"
|
||||||
|
- Токен не передан
|
||||||
|
- Неверный формат токена
|
||||||
|
- **Решение:** Проверьте заголовок Authorization
|
||||||
|
|
||||||
|
#### OpenID Connect не работает
|
||||||
|
```bash
|
||||||
|
# Проверьте .env файл
|
||||||
|
cat .env
|
||||||
|
|
||||||
|
# Проверьте переменные
|
||||||
|
echo $ZITADEL_ISSUER
|
||||||
|
echo $ZITADEL_CLIENT_ID
|
||||||
|
|
||||||
|
# Проверьте Redirect URI в ZITADEL
|
||||||
|
# Должен быть: http://localhost:8000/api/auth/oidc/zitadel/callback
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проблемы с серверами
|
||||||
|
|
||||||
|
#### Сервер не запускается
|
||||||
|
- Проверьте наличие `server.jar`
|
||||||
|
- Проверьте команду запуска
|
||||||
|
- Проверьте логи в консоли
|
||||||
|
- Проверьте права доступа к файлам
|
||||||
|
|
||||||
|
#### Консоль пустая
|
||||||
|
- Сервер еще не запущен
|
||||||
|
- WebSocket не подключен
|
||||||
|
- **Решение:** Перезапустите сервер
|
||||||
|
|
||||||
|
#### Статистика показывает 0
|
||||||
|
- Сервер остановлен
|
||||||
|
- Процесс завершился
|
||||||
|
- **Решение:** Запустите сервер
|
||||||
|
|
||||||
|
### Проблемы с файлами
|
||||||
|
|
||||||
|
#### "Файл не найден"
|
||||||
|
- Неверный путь
|
||||||
|
- Файл удален
|
||||||
|
- Нет прав доступа
|
||||||
|
- **Решение:** Проверьте путь и права
|
||||||
|
|
||||||
|
#### Не удается загрузить файл
|
||||||
|
- Файл слишком большой
|
||||||
|
- Нет места на диске
|
||||||
|
- **Решение:** Освободите место
|
||||||
|
|
||||||
|
#### Не удается переместить файл
|
||||||
|
- Файл открыт процессом
|
||||||
|
- Нет прав доступа
|
||||||
|
- **Решение:** Остановите сервер
|
||||||
|
|
||||||
|
### Проблемы с тикетами
|
||||||
|
|
||||||
|
#### Не приходят уведомления
|
||||||
|
- Проверьте интервал обновления (3 сек)
|
||||||
|
- Откройте консоль браузера (F12)
|
||||||
|
- Проверьте ошибки JavaScript
|
||||||
|
|
||||||
|
#### Сообщения не отправляются
|
||||||
|
- Проверьте подключение к интернету
|
||||||
|
- Проверьте токен авторизации
|
||||||
|
- Проверьте логи backend
|
||||||
|
|
||||||
|
### Проблемы с производительностью
|
||||||
|
|
||||||
|
#### Медленная работа
|
||||||
|
```bash
|
||||||
|
# Проверьте использование ресурсов
|
||||||
|
# Windows:
|
||||||
|
tasklist | findstr python
|
||||||
|
tasklist | findstr node
|
||||||
|
|
||||||
|
# Проверьте количество серверов
|
||||||
|
# Каждый сервер потребляет ресурсы
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Высокое использование CPU
|
||||||
|
- Много запущенных серверов
|
||||||
|
- Частые обновления статистики
|
||||||
|
- **Решение:** Остановите неиспользуемые серверы
|
||||||
|
|
||||||
|
### Логи и отладка
|
||||||
|
|
||||||
|
#### Включить подробные логи (Backend)
|
||||||
|
В `backend/main.py`:
|
||||||
|
```python
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Просмотр логов (Frontend)
|
||||||
|
Откройте консоль браузера (F12) → Console
|
||||||
|
|
||||||
|
#### Проверка API
|
||||||
|
```bash
|
||||||
|
# Проверьте доступность API
|
||||||
|
curl http://localhost:8000/api/auth/oidc/providers
|
||||||
|
|
||||||
|
# Проверьте токен
|
||||||
|
curl http://localhost:8000/api/auth/me \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Получение помощи
|
||||||
|
|
||||||
|
1. **Проверьте документацию** - возможно, ответ уже есть
|
||||||
|
2. **Проверьте логи** - backend и frontend
|
||||||
|
3. **Создайте тикет** - опишите проблему подробно
|
||||||
|
4. **GitHub Issues** - для багов и предложений
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Дополнительная информация
|
||||||
|
|
||||||
|
### Структура базы данных
|
||||||
|
|
||||||
|
#### users.json
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": {
|
||||||
|
"username": "string",
|
||||||
|
"password": "hashed_password",
|
||||||
|
"role": "admin|user|support|banned",
|
||||||
|
"servers": ["server1", "server2"],
|
||||||
|
"oidc_id": "provider:sub",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"name": "User Name",
|
||||||
|
"picture": "https://...",
|
||||||
|
"provider": "zitadel",
|
||||||
|
"created_at": "2026-01-15T10:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### tickets.json
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"1": {
|
||||||
|
"id": "1",
|
||||||
|
"title": "Проблема",
|
||||||
|
"description": "Описание",
|
||||||
|
"author": "username",
|
||||||
|
"status": "pending|in_progress|closed",
|
||||||
|
"created_at": "2026-01-15T10:00:00",
|
||||||
|
"updated_at": "2026-01-15T11:00:00",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"author": "username",
|
||||||
|
"text": "Сообщение",
|
||||||
|
"timestamp": "2026-01-15T10:00:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### panel_config.json (в папке сервера)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "server1",
|
||||||
|
"displayName": "My Server",
|
||||||
|
"startCommand": "java -Xmx2G -jar server.jar nogui",
|
||||||
|
"owner": "username"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Горячие клавиши
|
||||||
|
|
||||||
|
- **Ctrl + K** - Поиск (в файловом менеджере)
|
||||||
|
- **Enter** - Отправить сообщение (в тикете)
|
||||||
|
- **Esc** - Закрыть модальное окно
|
||||||
|
- **F5** - Обновить страницу
|
||||||
|
|
||||||
|
### Советы и трюки
|
||||||
|
|
||||||
|
#### Быстрое создание сервера
|
||||||
|
1. Создайте сервер через панель
|
||||||
|
2. Загрузите `server.jar` через файловый менеджер
|
||||||
|
3. Создайте `eula.txt` с содержимым `eula=true`
|
||||||
|
4. Запустите сервер
|
||||||
|
|
||||||
|
#### Массовое перемещение файлов
|
||||||
|
1. Выберите все файлы (чекбокс в заголовке)
|
||||||
|
2. Нажмите "Вырезать"
|
||||||
|
3. Перейдите в папку назначения
|
||||||
|
4. Нажмите "Вставить"
|
||||||
|
|
||||||
|
#### Мониторинг нескольких серверов
|
||||||
|
Откройте панель в нескольких вкладках браузера для одновременного мониторинга
|
||||||
|
|
||||||
|
#### Быстрый доступ к консоли
|
||||||
|
Добавьте панель в закладки браузера для быстрого доступа
|
||||||
|
|
||||||
|
### Ограничения
|
||||||
|
|
||||||
|
- **Максимальный размер файла:** зависит от настроек сервера
|
||||||
|
- **Количество серверов:** не ограничено (зависит от ресурсов)
|
||||||
|
- **Количество пользователей:** не ограничено
|
||||||
|
- **Длина сообщения в тикете:** не ограничена
|
||||||
|
- **Срок хранения логов:** 1000 последних строк
|
||||||
|
|
||||||
|
### Roadmap (Планы развития)
|
||||||
|
|
||||||
|
- [ ] Поддержка нескольких OIDC провайдеров
|
||||||
|
- [ ] Расписание запуска/остановки серверов
|
||||||
|
- [ ] Автоматические бэкапы
|
||||||
|
- [ ] Графики статистики
|
||||||
|
- [ ] Плагин-менеджер
|
||||||
|
- [ ] Мобильное приложение
|
||||||
|
- [ ] Push-уведомления
|
||||||
|
- [ ] Двухфакторная аутентификация
|
||||||
|
- [ ] Темная тема для консоли
|
||||||
|
- [ ] Экспорт логов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### Версия 1.0.0 (15.01.2026)
|
||||||
|
- ✨ Первый релиз
|
||||||
|
- ✅ Управление серверами
|
||||||
|
- ✅ Файловый менеджер
|
||||||
|
- ✅ Система тикетов
|
||||||
|
- ✅ Личный кабинет
|
||||||
|
- ✅ OpenID Connect (ZITADEL)
|
||||||
|
- ✅ 6 тем оформления
|
||||||
|
- ✅ Система уведомлений
|
||||||
|
- ✅ Управление пользователями
|
||||||
|
- ✅ Роли и права доступа
|
||||||
|
- ✅ WebSocket консоль
|
||||||
|
- ✅ Мониторинг ресурсов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT License - свободное использование
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Контакты и поддержка
|
||||||
|
|
||||||
|
- **Документация:** Этот файл
|
||||||
|
- **API Документация:** API.md
|
||||||
|
- **Тикеты:** Используйте систему тикетов в панели
|
||||||
|
- **GitHub:** [Ссылка на репозиторий]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Спасибо за использование MC Panel!** 🎮
|
||||||
|
|
||||||
|
**Версия документации:** 1.0.0
|
||||||
|
**Дата обновления:** 15 января 2026
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
# ✅ Исправление ошибки ModuleNotFoundError
|
|
||||||
|
|
||||||
## ❌ Ошибка
|
|
||||||
|
|
||||||
```
|
|
||||||
ModuleNotFoundError: No module named 'authlib.integrations.fastapi_client'
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✅ Решение
|
|
||||||
|
|
||||||
### Шаг 1: Установите зависимости
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
pip install authlib==1.3.0 httpx==0.26.0
|
|
||||||
```
|
|
||||||
|
|
||||||
Или установите все зависимости сразу:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
### Шаг 2: Проверьте установку
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -c "from authlib.integrations.starlette_client import OAuth; print('✓ OK')"
|
|
||||||
```
|
|
||||||
|
|
||||||
Должно вывести: `✓ OK`
|
|
||||||
|
|
||||||
### Шаг 3: Запустите сервер
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Должно появиться:
|
|
||||||
```
|
|
||||||
⚠ ZITADEL провайдер не настроен. Проверьте .env файл.
|
|
||||||
INFO: Started server process [12345]
|
|
||||||
INFO: Waiting for application startup.
|
|
||||||
INFO: Application startup complete.
|
|
||||||
INFO: Uvicorn running on http://0.0.0.0:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 Что было исправлено
|
|
||||||
|
|
||||||
### В файле `backend/main.py`
|
|
||||||
|
|
||||||
**Было:**
|
|
||||||
```python
|
|
||||||
from authlib.integrations.fastapi_client import OAuth
|
|
||||||
```
|
|
||||||
|
|
||||||
**Стало:**
|
|
||||||
```python
|
|
||||||
from authlib.integrations.starlette_client import OAuth
|
|
||||||
```
|
|
||||||
|
|
||||||
**Причина:** FastAPI основан на Starlette, поэтому в authlib 1.3.0 используется `starlette_client`, а не `fastapi_client`.
|
|
||||||
|
|
||||||
## 🔍 Проверка
|
|
||||||
|
|
||||||
### 1. Проверьте версию authlib
|
|
||||||
```bash
|
|
||||||
pip show authlib
|
|
||||||
```
|
|
||||||
|
|
||||||
Должно быть: `Version: 1.3.0`
|
|
||||||
|
|
||||||
### 2. Проверьте импорт
|
|
||||||
```bash
|
|
||||||
python -c "from authlib.integrations.starlette_client import OAuth; print('✓ Импорт работает')"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Проверьте main.py
|
|
||||||
```bash
|
|
||||||
python -c "import py_compile; py_compile.compile('backend/main.py', doraise=True); print('✓ Синтаксис правильный')"
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✅ Готово!
|
|
||||||
|
|
||||||
Ошибка исправлена. Теперь можете запускать сервер:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Следующие шаги
|
|
||||||
|
|
||||||
1. Настройте ZITADEL (см. `ZITADEL_QUICK_START.md`)
|
|
||||||
2. Обновите `.env` файл
|
|
||||||
3. Перезапустите сервер
|
|
||||||
4. Проверьте кнопку "Войти через ZITADEL"
|
|
||||||
|
|
||||||
## 📚 Дополнительная информация
|
|
||||||
|
|
||||||
- **Документация authlib:** https://docs.authlib.org/
|
|
||||||
- **FastAPI и Starlette:** https://fastapi.tiangolo.com/
|
|
||||||
- **Наша документация:** `README_OIDC.md`
|
|
||||||
209
ИТОГИ_РАБОТЫ.md
209
ИТОГИ_РАБОТЫ.md
@@ -1,209 +0,0 @@
|
|||||||
# 📊 Итоги работы - OpenID Connect с ZITADEL
|
|
||||||
|
|
||||||
## ✅ Задача выполнена
|
|
||||||
|
|
||||||
**Задача:** Добавить провайдера OpenID Connect ZITADEL и удалить провайдеров Google, Microsoft, Discord, GitHub
|
|
||||||
|
|
||||||
**Статус:** ✅ Полностью выполнено
|
|
||||||
|
|
||||||
## 📝 Что было сделано
|
|
||||||
|
|
||||||
### 1. Backend изменения
|
|
||||||
|
|
||||||
#### `backend/main.py`
|
|
||||||
**Изменено:**
|
|
||||||
- Упрощена инициализация OAuth (строки 28-42)
|
|
||||||
- Удалён цикл по провайдерам
|
|
||||||
- Оставлена только регистрация ZITADEL
|
|
||||||
- Добавлено логирование статуса регистрации
|
|
||||||
|
|
||||||
- Улучшена обработка callback (строки 200-230)
|
|
||||||
- Добавлена проверка наличия userinfo
|
|
||||||
- Улучшена обработка id_token
|
|
||||||
- Добавлено детальное логирование ошибок
|
|
||||||
|
|
||||||
**Результат:** Код стал проще и понятнее, работает только с ZITADEL
|
|
||||||
|
|
||||||
#### `backend/oidc_config.py`
|
|
||||||
**Изменено:**
|
|
||||||
- Удалены все провайдеры кроме ZITADEL
|
|
||||||
- Оставлена только конфигурация ZITADEL:
|
|
||||||
- `ZITADEL_ISSUER` - URL инстанса
|
|
||||||
- `ZITADEL_CLIENT_ID` - ID приложения
|
|
||||||
- `ZITADEL_CLIENT_SECRET` - Секретный ключ
|
|
||||||
|
|
||||||
**Результат:** Чистая конфигурация только для ZITADEL
|
|
||||||
|
|
||||||
#### `backend/.env.example`
|
|
||||||
**Статус:** Уже был обновлён ранее
|
|
||||||
- Содержит настройки ZITADEL
|
|
||||||
- Удалены старые провайдеры
|
|
||||||
|
|
||||||
### 2. Frontend изменения
|
|
||||||
|
|
||||||
#### `frontend/src/components/Auth.jsx`
|
|
||||||
**Статус:** Уже работает корректно
|
|
||||||
- Динамически загружает провайдеров
|
|
||||||
- Показывает кнопку ZITADEL если настроен
|
|
||||||
- Скрывает если не настроен
|
|
||||||
|
|
||||||
#### `frontend/src/App.jsx`
|
|
||||||
**Статус:** Уже работает корректно
|
|
||||||
- Обрабатывает callback от ZITADEL
|
|
||||||
- Сохраняет токен
|
|
||||||
- Очищает URL
|
|
||||||
|
|
||||||
#### `frontend/src/components/AuthCallback.jsx`
|
|
||||||
**Удалено:** ❌
|
|
||||||
- Компонент не использовался
|
|
||||||
- Логика перенесена в App.jsx
|
|
||||||
|
|
||||||
### 3. Документация
|
|
||||||
|
|
||||||
#### Обновлено
|
|
||||||
- **`OPENID_CONNECT_SETUP.md`** - Полностью переписана для ZITADEL
|
|
||||||
- Удалены инструкции для Google, Microsoft, Discord, GitHub
|
|
||||||
- Добавлена подробная инструкция для ZITADEL
|
|
||||||
- Обновлены примеры конфигурации
|
|
||||||
|
|
||||||
#### Создано
|
|
||||||
- **`ZITADEL_QUICK_START.md`** - Быстрый старт (4 шага)
|
|
||||||
- **`OIDC_CHANGES.md`** - Технические детали изменений
|
|
||||||
- **`ГОТОВО_OIDC.md`** - Итоговая инструкция
|
|
||||||
- **`СЛЕДУЮЩИЕ_ШАГИ.md`** - Что делать дальше
|
|
||||||
- **`ИТОГИ_РАБОТЫ.md`** - Этот файл
|
|
||||||
|
|
||||||
## 🔍 Проверка качества
|
|
||||||
|
|
||||||
### Синтаксис Python
|
|
||||||
```bash
|
|
||||||
✓ backend/main.py - OK
|
|
||||||
✓ backend/oidc_config.py - OK
|
|
||||||
```
|
|
||||||
|
|
||||||
### Импорты
|
|
||||||
```bash
|
|
||||||
✓ oidc_config импортируется корректно
|
|
||||||
✓ Все зависимости на месте
|
|
||||||
```
|
|
||||||
|
|
||||||
### Структура файлов
|
|
||||||
```
|
|
||||||
backend/
|
|
||||||
├── main.py ✓
|
|
||||||
├── oidc_config.py ✓
|
|
||||||
├── .env.example ✓
|
|
||||||
└── requirements.txt ✓
|
|
||||||
|
|
||||||
frontend/
|
|
||||||
└── src/
|
|
||||||
├── App.jsx ✓
|
|
||||||
└── components/
|
|
||||||
└── Auth.jsx ✓
|
|
||||||
|
|
||||||
docs/
|
|
||||||
├── OPENID_CONNECT_SETUP.md ✓
|
|
||||||
├── ZITADEL_QUICK_START.md ✓
|
|
||||||
├── OIDC_CHANGES.md ✓
|
|
||||||
├── ГОТОВО_OIDC.md ✓
|
|
||||||
└── СЛЕДУЮЩИЕ_ШАГИ.md ✓
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 Статистика
|
|
||||||
|
|
||||||
### Файлы
|
|
||||||
- **Изменено:** 2 файла (main.py, OPENID_CONNECT_SETUP.md)
|
|
||||||
- **Создано:** 5 файлов документации
|
|
||||||
- **Удалено:** 1 файл (AuthCallback.jsx)
|
|
||||||
|
|
||||||
### Код
|
|
||||||
- **Строк изменено:** ~50 строк
|
|
||||||
- **Строк добавлено:** ~200 строк (документация)
|
|
||||||
- **Упрощено:** OAuth инициализация (с 20 строк до 10)
|
|
||||||
|
|
||||||
### Провайдеры
|
|
||||||
- **Было:** Google, Microsoft, Discord, GitHub (4)
|
|
||||||
- **Стало:** ZITADEL (1)
|
|
||||||
- **Упрощение:** 75%
|
|
||||||
|
|
||||||
## 🎯 Результаты
|
|
||||||
|
|
||||||
### Функциональность
|
|
||||||
✅ Вход через ZITADEL работает
|
|
||||||
✅ Автоматическое создание пользователей
|
|
||||||
✅ Обработка callback
|
|
||||||
✅ Генерация JWT токенов
|
|
||||||
✅ Обновление данных пользователя
|
|
||||||
|
|
||||||
### Код
|
|
||||||
✅ Чище и проще
|
|
||||||
✅ Меньше зависимостей
|
|
||||||
✅ Лучше читаемость
|
|
||||||
✅ Подробное логирование
|
|
||||||
✅ Обработка ошибок
|
|
||||||
|
|
||||||
### Документация
|
|
||||||
✅ Полная инструкция по ZITADEL
|
|
||||||
✅ Быстрый старт
|
|
||||||
✅ Решение проблем
|
|
||||||
✅ Технические детали
|
|
||||||
✅ Примеры конфигурации
|
|
||||||
|
|
||||||
## 🚀 Готово к использованию
|
|
||||||
|
|
||||||
Система полностью готова к работе!
|
|
||||||
|
|
||||||
### Что нужно сделать пользователю:
|
|
||||||
1. Создать приложение в ZITADEL (5 минут)
|
|
||||||
2. Настроить .env файл (1 минута)
|
|
||||||
3. Запустить backend и frontend (30 секунд)
|
|
||||||
4. Протестировать вход (10 секунд)
|
|
||||||
|
|
||||||
**Общее время настройки: ~7 минут**
|
|
||||||
|
|
||||||
## 📚 Документация для пользователя
|
|
||||||
|
|
||||||
### Начало работы
|
|
||||||
→ **`СЛЕДУЮЩИЕ_ШАГИ.md`** - Что делать сейчас
|
|
||||||
|
|
||||||
### Быстрая настройка
|
|
||||||
→ **`ZITADEL_QUICK_START.md`** - 4 простых шага
|
|
||||||
|
|
||||||
### Подробная инструкция
|
|
||||||
→ **`OPENID_CONNECT_SETUP.md`** - Полное руководство
|
|
||||||
|
|
||||||
### Для разработчиков
|
|
||||||
→ **`OIDC_CHANGES.md`** - Технические детали
|
|
||||||
|
|
||||||
## ✨ Преимущества решения
|
|
||||||
|
|
||||||
### Для пользователей
|
|
||||||
- 🔐 Безопасный вход через ZITADEL
|
|
||||||
- 👤 Автоматическое создание аккаунта
|
|
||||||
- 🔄 Синхронизация данных
|
|
||||||
- 🎨 Красивый интерфейс
|
|
||||||
|
|
||||||
### Для администраторов
|
|
||||||
- 📊 Централизованное управление
|
|
||||||
- 🔧 Простая настройка
|
|
||||||
- 📝 Подробные логи
|
|
||||||
- 🛡️ Высокая безопасность
|
|
||||||
|
|
||||||
### Для разработчиков
|
|
||||||
- 💻 Чистый код
|
|
||||||
- 📚 Хорошая документация
|
|
||||||
- 🔍 Легко отлаживать
|
|
||||||
- 🚀 Легко расширять
|
|
||||||
|
|
||||||
## 🎉 Итог
|
|
||||||
|
|
||||||
**OpenID Connect с ZITADEL полностью интегрирован и готов к использованию!**
|
|
||||||
|
|
||||||
Все задачи выполнены:
|
|
||||||
- ✅ Добавлен ZITADEL провайдер
|
|
||||||
- ✅ Удалены Google, Microsoft, Discord, GitHub
|
|
||||||
- ✅ Упрощён код
|
|
||||||
- ✅ Создана документация
|
|
||||||
- ✅ Протестирована работа
|
|
||||||
|
|
||||||
**Система готова к продакшену!** 🚀
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
# ✅ Личный кабинет готов!
|
|
||||||
|
|
||||||
## 🎉 Что добавлено
|
|
||||||
|
|
||||||
### Кнопка "Личный кабинет"
|
|
||||||
Расположена в header рядом с кнопкой "Тикеты". Доступна всем пользователям.
|
|
||||||
|
|
||||||
### Три вкладки
|
|
||||||
|
|
||||||
#### 1. 📊 Обзор
|
|
||||||
**Информация о пользователе:**
|
|
||||||
- Имя пользователя
|
|
||||||
- Роль с цветным бейджем
|
|
||||||
- Аватар
|
|
||||||
|
|
||||||
**Статистика:**
|
|
||||||
- 🖥️ **Серверы**: всего, мои, доступные
|
|
||||||
- 🎫 **Тикеты**: всего, на рассмотрении, в работе, закрыто
|
|
||||||
- 🛡️ **Роль**: название и описание прав
|
|
||||||
|
|
||||||
**Список серверов:**
|
|
||||||
- Все серверы пользователя
|
|
||||||
- Название и ID
|
|
||||||
- Красивые карточки
|
|
||||||
|
|
||||||
#### 2. 👤 Имя пользователя
|
|
||||||
**Возможности:**
|
|
||||||
- Просмотр текущего имени
|
|
||||||
- Ввод нового имени (минимум 3 символа)
|
|
||||||
- Подтверждение паролем
|
|
||||||
- Автоматический перелогин
|
|
||||||
|
|
||||||
**Что обновляется:**
|
|
||||||
- Владельцы серверов
|
|
||||||
- Доступы к серверам
|
|
||||||
- JWT токен
|
|
||||||
|
|
||||||
#### 3. 🔒 Пароль
|
|
||||||
**Возможности:**
|
|
||||||
- Ввод текущего пароля
|
|
||||||
- Ввод нового пароля (минимум 6 символов)
|
|
||||||
- Подтверждение нового пароля
|
|
||||||
- Показ/скрытие паролей (иконка глаза)
|
|
||||||
|
|
||||||
**Безопасность:**
|
|
||||||
- Проверка текущего пароля
|
|
||||||
- Проверка совпадения новых паролей
|
|
||||||
- Хеширование (bcrypt)
|
|
||||||
|
|
||||||
## 🚀 Как использовать
|
|
||||||
|
|
||||||
### Открыть личный кабинет
|
|
||||||
1. Войдите в панель
|
|
||||||
2. Нажмите кнопку "Личный кабинет" в header
|
|
||||||
3. Откроется страница с вкладками
|
|
||||||
|
|
||||||
### Посмотреть статистику
|
|
||||||
1. Откройте вкладку "Обзор"
|
|
||||||
2. Посмотрите информацию о профиле
|
|
||||||
3. Посмотрите статистику
|
|
||||||
4. Посмотрите список серверов
|
|
||||||
|
|
||||||
### Изменить имя пользователя
|
|
||||||
1. Откройте вкладку "Имя пользователя"
|
|
||||||
2. Введите новое имя (минимум 3 символа)
|
|
||||||
3. Введите текущий пароль
|
|
||||||
4. Нажмите "Изменить имя пользователя"
|
|
||||||
5. Вы будете автоматически перелогинены
|
|
||||||
|
|
||||||
⚠️ **Важно**: После изменения имени обновятся все серверы и доступы.
|
|
||||||
|
|
||||||
### Изменить пароль
|
|
||||||
1. Откройте вкладку "Пароль"
|
|
||||||
2. Введите текущий пароль
|
|
||||||
3. Введите новый пароль (минимум 6 символов)
|
|
||||||
4. Подтвердите новый пароль
|
|
||||||
5. Нажмите "Изменить пароль"
|
|
||||||
|
|
||||||
⚠️ **Важно**: После изменения пароля используйте новый пароль для входа.
|
|
||||||
|
|
||||||
## 🎨 Дизайн
|
|
||||||
|
|
||||||
### Современный интерфейс
|
|
||||||
- Вкладки с плавными переходами
|
|
||||||
- Цветные карточки статистики
|
|
||||||
- Иконки для каждого раздела
|
|
||||||
- Адаптивный дизайн
|
|
||||||
|
|
||||||
### Формы
|
|
||||||
- Валидация полей
|
|
||||||
- Показ/скрытие паролей
|
|
||||||
- Предупреждения
|
|
||||||
- Индикация загрузки
|
|
||||||
|
|
||||||
### Цветовые схемы
|
|
||||||
- Работает со всеми 5 темами
|
|
||||||
- Градиентный логотип
|
|
||||||
- Цветные бейджи ролей
|
|
||||||
|
|
||||||
## 📋 API Endpoints
|
|
||||||
|
|
||||||
### PUT /api/profile/username
|
|
||||||
Изменить имя пользователя
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X PUT http://localhost:8000/api/profile/username \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"new_username": "NewUsername",
|
|
||||||
"password": "current_password"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### PUT /api/profile/password
|
|
||||||
Изменить пароль
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X PUT http://localhost:8000/api/profile/password \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"old_password": "old_password",
|
|
||||||
"new_password": "new_password"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /api/profile/stats
|
|
||||||
Получить статистику профиля
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X GET http://localhost:8000/api/profile/stats \
|
|
||||||
-H "Authorization: Bearer YOUR_TOKEN"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔐 Безопасность
|
|
||||||
|
|
||||||
### Изменение имени
|
|
||||||
1. ✅ Проверка пароля
|
|
||||||
2. ✅ Проверка уникальности
|
|
||||||
3. ✅ Обновление владельцев серверов
|
|
||||||
4. ✅ Обновление доступов
|
|
||||||
5. ✅ Новый JWT токен
|
|
||||||
|
|
||||||
### Изменение пароля
|
|
||||||
1. ✅ Проверка текущего пароля
|
|
||||||
2. ✅ Проверка длины (минимум 6 символов)
|
|
||||||
3. ✅ Хеширование (bcrypt)
|
|
||||||
|
|
||||||
## ✅ Готово!
|
|
||||||
|
|
||||||
Личный кабинет полностью интегрирован в MC Panel. Все пользователи могут:
|
|
||||||
- 📊 Просматривать статистику
|
|
||||||
- 👤 Изменять имя пользователя
|
|
||||||
- 🔒 Изменять пароль
|
|
||||||
- 🖥️ Видеть свои серверы
|
|
||||||
- 🎫 Видеть статистику по тикетам
|
|
||||||
|
|
||||||
### Тестирование
|
|
||||||
|
|
||||||
1. **Войдите в панель**
|
|
||||||
- Логин: Sofa12345
|
|
||||||
- Пароль: arkonsad123
|
|
||||||
|
|
||||||
2. **Откройте личный кабинет**
|
|
||||||
- Нажмите кнопку "Личный кабинет"
|
|
||||||
|
|
||||||
3. **Посмотрите статистику**
|
|
||||||
- Вкладка "Обзор"
|
|
||||||
|
|
||||||
4. **Измените имя (опционально)**
|
|
||||||
- Вкладка "Имя пользователя"
|
|
||||||
- Введите новое имя и пароль
|
|
||||||
|
|
||||||
5. **Измените пароль (опционально)**
|
|
||||||
- Вкладка "Пароль"
|
|
||||||
- Введите старый и новый пароли
|
|
||||||
|
|
||||||
## 🎯 Что дальше?
|
|
||||||
|
|
||||||
Система личного кабинета готова к использованию. Теперь пользователи могут полностью управлять своим профилем!
|
|
||||||
|
|
||||||
**Наслаждайтесь! 🚀**
|
|
||||||
123
РЕЗЮМЕ.md
123
РЕЗЮМЕ.md
@@ -1,123 +0,0 @@
|
|||||||
# ✅ РЕЗЮМЕ - OpenID Connect готов!
|
|
||||||
|
|
||||||
## 🎯 Задача
|
|
||||||
|
|
||||||
Добавить провайдера OpenID Connect **ZITADEL** и удалить провайдеров Google, Microsoft, Discord, GitHub.
|
|
||||||
|
|
||||||
## ✅ Выполнено
|
|
||||||
|
|
||||||
Задача **полностью выполнена**! Система готова к использованию.
|
|
||||||
|
|
||||||
## 📋 Что сделано
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- ✅ Упрощена инициализация OAuth (только ZITADEL)
|
|
||||||
- ✅ Улучшена обработка callback
|
|
||||||
- ✅ Добавлено логирование
|
|
||||||
- ✅ Обработка ошибок
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- ✅ Кнопка "Войти через ZITADEL" 🔐
|
|
||||||
- ✅ Автоматический вход после OAuth
|
|
||||||
- ✅ Обработка токенов
|
|
||||||
|
|
||||||
### Документация
|
|
||||||
- ✅ Быстрый старт (4 шага)
|
|
||||||
- ✅ Подробная инструкция
|
|
||||||
- ✅ Технические детали
|
|
||||||
- ✅ Схема работы
|
|
||||||
- ✅ Решение проблем
|
|
||||||
|
|
||||||
## 📁 Файлы
|
|
||||||
|
|
||||||
### Изменено
|
|
||||||
- `backend/main.py` - OAuth инициализация
|
|
||||||
- `OPENID_CONNECT_SETUP.md` - Обновлена для ZITADEL
|
|
||||||
|
|
||||||
### Создано
|
|
||||||
- `ZITADEL_QUICK_START.md` - Быстрый старт
|
|
||||||
- `OIDC_CHANGES.md` - Технические детали
|
|
||||||
- `ГОТОВО_OIDC.md` - Итоговая инструкция
|
|
||||||
- `СЛЕДУЮЩИЕ_ШАГИ.md` - Что делать дальше
|
|
||||||
- `ИТОГИ_РАБОТЫ.md` - Полный отчёт
|
|
||||||
- `СХЕМА_РАБОТЫ.md` - Визуальная схема
|
|
||||||
- `РЕЗЮМЕ.md` - Этот файл
|
|
||||||
|
|
||||||
### Удалено
|
|
||||||
- `frontend/src/components/AuthCallback.jsx` - Не используется
|
|
||||||
|
|
||||||
## 🚀 Как использовать
|
|
||||||
|
|
||||||
### 1. Настройте ZITADEL (5 минут)
|
|
||||||
```
|
|
||||||
1. Зайти на zitadel.cloud
|
|
||||||
2. Создать приложение (Web, Code with PKCE)
|
|
||||||
3. Добавить Redirect URI
|
|
||||||
4. Скопировать Client ID и Secret
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Обновите .env (1 минута)
|
|
||||||
```bash
|
|
||||||
ZITADEL_ISSUER=https://your-instance.zitadel.cloud
|
|
||||||
ZITADEL_CLIENT_ID=123456789012345678@your-project
|
|
||||||
ZITADEL_CLIENT_SECRET=your-secret-here
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Запустите (30 секунд)
|
|
||||||
```bash
|
|
||||||
# Backend
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
|
|
||||||
# Frontend
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Проверьте (10 секунд)
|
|
||||||
Откройте http://localhost:3000 → Кнопка "Войти через ZITADEL" 🔐
|
|
||||||
|
|
||||||
## 📚 Документация
|
|
||||||
|
|
||||||
| Файл | Описание |
|
|
||||||
|------|----------|
|
|
||||||
| `СЛЕДУЮЩИЕ_ШАГИ.md` | Что делать сейчас |
|
|
||||||
| `ZITADEL_QUICK_START.md` | Быстрый старт (4 шага) |
|
|
||||||
| `OPENID_CONNECT_SETUP.md` | Подробная инструкция |
|
|
||||||
| `OIDC_CHANGES.md` | Технические детали |
|
|
||||||
| `СХЕМА_РАБОТЫ.md` | Визуальная схема |
|
|
||||||
| `ИТОГИ_РАБОТЫ.md` | Полный отчёт |
|
|
||||||
|
|
||||||
## ✨ Результат
|
|
||||||
|
|
||||||
### Функциональность
|
|
||||||
- ✅ Вход через ZITADEL
|
|
||||||
- ✅ Автоматическое создание пользователей
|
|
||||||
- ✅ Обновление данных при входе
|
|
||||||
- ✅ JWT токены
|
|
||||||
- ✅ Безопасность (PKCE, state, nonce)
|
|
||||||
|
|
||||||
### Код
|
|
||||||
- ✅ Чище и проще
|
|
||||||
- ✅ Меньше зависимостей
|
|
||||||
- ✅ Лучше читаемость
|
|
||||||
- ✅ Подробное логирование
|
|
||||||
|
|
||||||
### Документация
|
|
||||||
- ✅ 7 файлов документации
|
|
||||||
- ✅ Быстрый старт
|
|
||||||
- ✅ Подробные инструкции
|
|
||||||
- ✅ Визуальные схемы
|
|
||||||
- ✅ Решение проблем
|
|
||||||
|
|
||||||
## 🎉 Готово!
|
|
||||||
|
|
||||||
**OpenID Connect с ZITADEL полностью интегрирован!**
|
|
||||||
|
|
||||||
Система готова к использованию. Следуйте инструкциям в `СЛЕДУЮЩИЕ_ШАГИ.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Время настройки:** ~7 минут
|
|
||||||
**Сложность:** Низкая
|
|
||||||
**Статус:** ✅ Готово к продакшену
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
# 📋 Следующие шаги - OpenID Connect готов!
|
|
||||||
|
|
||||||
## ✅ Что сделано
|
|
||||||
|
|
||||||
Интеграция OpenID Connect с ZITADEL **полностью завершена**!
|
|
||||||
|
|
||||||
## 🎯 Что нужно сделать сейчас
|
|
||||||
|
|
||||||
### 1. Настроить ZITADEL (5 минут)
|
|
||||||
|
|
||||||
```
|
|
||||||
1. Зайти на zitadel.cloud
|
|
||||||
2. Создать приложение (Web, Code with PKCE)
|
|
||||||
3. Добавить Redirect URI: http://localhost:8000/api/auth/oidc/zitadel/callback
|
|
||||||
4. Скопировать Client ID и Secret
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Обновить .env файл (1 минута)
|
|
||||||
|
|
||||||
Откройте `backend/.env` и добавьте:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ZITADEL_ISSUER=https://your-instance.zitadel.cloud
|
|
||||||
ZITADEL_CLIENT_ID=123456789012345678@your-project
|
|
||||||
ZITADEL_CLIENT_SECRET=your-secret-here
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Запустить (30 секунд)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backend
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
|
|
||||||
# Frontend (новый терминал)
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Проверить (10 секунд)
|
|
||||||
|
|
||||||
Откройте http://localhost:3000 → Увидите кнопку "Войти через ZITADEL" 🔐
|
|
||||||
|
|
||||||
## 📚 Документация
|
|
||||||
|
|
||||||
### Быстрый старт
|
|
||||||
→ **`ZITADEL_QUICK_START.md`** - 4 простых шага
|
|
||||||
|
|
||||||
### Подробная инструкция
|
|
||||||
→ **`OPENID_CONNECT_SETUP.md`** - Полное руководство
|
|
||||||
|
|
||||||
### Технические детали
|
|
||||||
→ **`OIDC_CHANGES.md`** - Что изменилось в коде
|
|
||||||
|
|
||||||
## 🔍 Проверка работы
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Должно появиться:
|
|
||||||
```
|
|
||||||
✓ ZITADEL провайдер зарегистрирован: https://your-instance.zitadel.cloud
|
|
||||||
```
|
|
||||||
|
|
||||||
Если видите:
|
|
||||||
```
|
|
||||||
⚠ ZITADEL провайдер не настроен. Проверьте .env файл.
|
|
||||||
```
|
|
||||||
→ Проверьте настройки в `.env`
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
Откройте http://localhost:3000
|
|
||||||
|
|
||||||
Должна быть кнопка:
|
|
||||||
```
|
|
||||||
🔐 Войти через ZITADEL
|
|
||||||
```
|
|
||||||
|
|
||||||
Если кнопки нет:
|
|
||||||
- Проверьте что backend запущен
|
|
||||||
- Проверьте консоль браузера (F12)
|
|
||||||
- Проверьте `.env` файл
|
|
||||||
|
|
||||||
## 🎨 Как выглядит
|
|
||||||
|
|
||||||
### Страница входа
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────┐
|
|
||||||
│ MC Panel Logo │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────┐ │
|
|
||||||
│ │ Имя пользователя │ │
|
|
||||||
│ └───────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────┐ │
|
|
||||||
│ │ Пароль │ │
|
|
||||||
│ └───────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────┐ │
|
|
||||||
│ │ Войти │ │
|
|
||||||
│ └───────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ─── Или войдите через ─── │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────┐ │
|
|
||||||
│ │ 🔐 Войти через ZITADEL │ │
|
|
||||||
│ └───────────────────────────┘ │
|
|
||||||
└─────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 Процесс входа
|
|
||||||
|
|
||||||
1. Пользователь нажимает "Войти через ZITADEL"
|
|
||||||
2. Перенаправление на ZITADEL
|
|
||||||
3. Пользователь вводит логин/пароль в ZITADEL
|
|
||||||
4. ZITADEL возвращает на панель
|
|
||||||
5. Автоматическое создание пользователя
|
|
||||||
6. Автоматический вход в систему
|
|
||||||
|
|
||||||
**Всё происходит автоматически!**
|
|
||||||
|
|
||||||
## 🎁 Бонусы
|
|
||||||
|
|
||||||
### Для пользователей
|
|
||||||
- ✅ Не нужно запоминать ещё один пароль
|
|
||||||
- ✅ Безопасный вход через ZITADEL
|
|
||||||
- ✅ Можно включить 2FA в ZITADEL
|
|
||||||
|
|
||||||
### Для администраторов
|
|
||||||
- ✅ Централизованное управление пользователями
|
|
||||||
- ✅ Автоматическое создание аккаунтов
|
|
||||||
- ✅ Логи входов
|
|
||||||
|
|
||||||
## 🚀 Готово к использованию!
|
|
||||||
|
|
||||||
Система полностью настроена и готова к работе.
|
|
||||||
|
|
||||||
**Следующий шаг:** Настройте ZITADEL и протестируйте вход!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Нужна помощь?** Смотрите `ZITADEL_QUICK_START.md`
|
|
||||||
282
СХЕМА_РАБОТЫ.md
282
СХЕМА_РАБОТЫ.md
@@ -1,282 +0,0 @@
|
|||||||
# 🔄 Схема работы OpenID Connect с ZITADEL
|
|
||||||
|
|
||||||
## Визуальная схема
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────┐
|
|
||||||
│ Пользователь│
|
|
||||||
└──────┬──────┘
|
|
||||||
│
|
|
||||||
│ 1. Открывает страницу входа
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ Frontend (React) │
|
|
||||||
│ http://localhost:3000 │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────────┐ │
|
|
||||||
│ │ Форма входа │ │
|
|
||||||
│ │ - Логин/Пароль │ │
|
|
||||||
│ │ - Кнопка ZITADEL 🔐 │ │
|
|
||||||
│ └───────────────────────────────┘ │
|
|
||||||
└──────────────┬──────────────────────┘
|
|
||||||
│
|
|
||||||
│ 2. Нажимает "Войти через ZITADEL"
|
|
||||||
│ GET /api/auth/oidc/zitadel/login
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ Backend (FastAPI) │
|
|
||||||
│ http://localhost:8000 │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────────┐ │
|
|
||||||
│ │ OAuth Client │ │
|
|
||||||
│ │ - Создаёт authorize URL │ │
|
|
||||||
│ │ - Добавляет state, nonce │ │
|
|
||||||
│ └───────────────────────────────┘ │
|
|
||||||
└──────────────┬──────────────────────┘
|
|
||||||
│
|
|
||||||
│ 3. Redirect на ZITADEL
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ZITADEL │
|
|
||||||
│ https://your-instance.zitadel.cloud│
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────────┐ │
|
|
||||||
│ │ Страница входа │ │
|
|
||||||
│ │ - Email/Username │ │
|
|
||||||
│ │ - Password │ │
|
|
||||||
│ │ - 2FA (опционально) │ │
|
|
||||||
│ └───────────────────────────────┘ │
|
|
||||||
└──────────────┬──────────────────────┘
|
|
||||||
│
|
|
||||||
│ 4. Пользователь вводит данные
|
|
||||||
│ 5. ZITADEL проверяет
|
|
||||||
│ 6. Redirect с code
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ Backend (FastAPI) │
|
|
||||||
│ /api/auth/oidc/zitadel/callback │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────────┐ │
|
|
||||||
│ │ 7. Обмен code на token │ │
|
|
||||||
│ │ POST /oauth/token │ │
|
|
||||||
│ └───────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────────┐ │
|
|
||||||
│ │ 8. Получение userinfo │ │
|
|
||||||
│ │ - email │ │
|
|
||||||
│ │ - name │ │
|
|
||||||
│ │ - sub (user ID) │ │
|
|
||||||
│ └───────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────────┐ │
|
|
||||||
│ │ 9. Создание/обновление │ │
|
|
||||||
│ │ пользователя в users.json │ │
|
|
||||||
│ └───────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────────┐ │
|
|
||||||
│ │ 10. Генерация JWT токена │ │
|
|
||||||
│ │ для MC Panel │ │
|
|
||||||
│ └───────────────────────────────┘ │
|
|
||||||
└──────────────┬──────────────────────┘
|
|
||||||
│
|
|
||||||
│ 11. Redirect на frontend
|
|
||||||
│ ?token=xxx&username=yyy
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ Frontend (React) │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────────┐ │
|
|
||||||
│ │ 12. Обработка callback │ │
|
|
||||||
│ │ - Извлечение token │ │
|
|
||||||
│ │ - Сохранение в localStorage│ │
|
|
||||||
│ │ - Очистка URL │ │
|
|
||||||
│ └───────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌───────────────────────────────┐ │
|
|
||||||
│ │ 13. Автоматический вход │ │
|
|
||||||
│ │ - Загрузка данных │ │
|
|
||||||
│ │ - Показ панели │ │
|
|
||||||
│ └───────────────────────────────┘ │
|
|
||||||
└──────────────┬──────────────────────┘
|
|
||||||
│
|
|
||||||
│ 14. Пользователь в системе!
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ MC Panel Dashboard │
|
|
||||||
│ - Серверы │
|
|
||||||
│ - Тикеты │
|
|
||||||
│ - Личный кабинет │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Детальный поток данных
|
|
||||||
|
|
||||||
### Шаг 1-2: Инициация входа
|
|
||||||
```
|
|
||||||
Пользователь → Frontend
|
|
||||||
↓
|
|
||||||
Frontend → Backend: GET /api/auth/oidc/zitadel/login
|
|
||||||
↓
|
|
||||||
Backend создаёт OAuth URL:
|
|
||||||
- client_id
|
|
||||||
- redirect_uri
|
|
||||||
- scope: openid email profile
|
|
||||||
- state (CSRF защита)
|
|
||||||
- nonce (replay защита)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Шаг 3-6: Аутентификация в ZITADEL
|
|
||||||
```
|
|
||||||
Backend → ZITADEL: Redirect на /oauth/authorize
|
|
||||||
↓
|
|
||||||
ZITADEL показывает форму входа
|
|
||||||
↓
|
|
||||||
Пользователь вводит данные
|
|
||||||
↓
|
|
||||||
ZITADEL проверяет учётные данные
|
|
||||||
↓
|
|
||||||
ZITADEL → Backend: Redirect с code
|
|
||||||
URL: /callback?code=xxx&state=yyy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Шаг 7-8: Получение данных
|
|
||||||
```
|
|
||||||
Backend → ZITADEL: POST /oauth/token
|
|
||||||
Параметры:
|
|
||||||
- code
|
|
||||||
- client_id
|
|
||||||
- client_secret
|
|
||||||
- redirect_uri
|
|
||||||
↓
|
|
||||||
ZITADEL → Backend: access_token + id_token
|
|
||||||
↓
|
|
||||||
Backend извлекает userinfo:
|
|
||||||
{
|
|
||||||
"sub": "123456789012345678",
|
|
||||||
"email": "user@example.com",
|
|
||||||
"name": "John Doe",
|
|
||||||
"picture": "https://..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Шаг 9-10: Создание пользователя
|
|
||||||
```
|
|
||||||
Backend проверяет users.json:
|
|
||||||
- Ищет по oidc_id = "zitadel:123456789012345678"
|
|
||||||
|
|
||||||
Если найден:
|
|
||||||
- Обновляет email, name, picture
|
|
||||||
|
|
||||||
Если не найден:
|
|
||||||
- Создаёт нового пользователя
|
|
||||||
- Генерирует username из email
|
|
||||||
- Роль: "user"
|
|
||||||
- Пустой пароль (OIDC пользователь)
|
|
||||||
|
|
||||||
Backend создаёт JWT токен:
|
|
||||||
{
|
|
||||||
"sub": "john_doe",
|
|
||||||
"role": "user",
|
|
||||||
"exp": 1234567890
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Шаг 11-14: Возврат в приложение
|
|
||||||
```
|
|
||||||
Backend → Frontend: Redirect
|
|
||||||
URL: http://localhost:3000/?token=xxx&username=yyy
|
|
||||||
↓
|
|
||||||
Frontend (useEffect):
|
|
||||||
- Извлекает token и username из URL
|
|
||||||
- Сохраняет в localStorage
|
|
||||||
- Очищает URL (history.replaceState)
|
|
||||||
- Обновляет состояние (setToken, setUser)
|
|
||||||
↓
|
|
||||||
Frontend загружает данные:
|
|
||||||
- GET /api/auth/me (проверка токена)
|
|
||||||
- GET /api/servers (список серверов)
|
|
||||||
↓
|
|
||||||
Пользователь видит панель управления
|
|
||||||
```
|
|
||||||
|
|
||||||
## Структура данных
|
|
||||||
|
|
||||||
### ZITADEL userinfo
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"sub": "123456789012345678",
|
|
||||||
"email": "user@example.com",
|
|
||||||
"email_verified": true,
|
|
||||||
"name": "John Doe",
|
|
||||||
"given_name": "John",
|
|
||||||
"family_name": "Doe",
|
|
||||||
"picture": "https://avatar.url",
|
|
||||||
"locale": "en"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### MC Panel user (users.json)
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"john_doe": {
|
|
||||||
"username": "john_doe",
|
|
||||||
"password": "",
|
|
||||||
"role": "user",
|
|
||||||
"servers": [],
|
|
||||||
"oidc_id": "zitadel:123456789012345678",
|
|
||||||
"email": "user@example.com",
|
|
||||||
"name": "John Doe",
|
|
||||||
"picture": "https://avatar.url",
|
|
||||||
"provider": "zitadel",
|
|
||||||
"created_at": "2026-01-15T12:00:00"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### JWT токен (MC Panel)
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"sub": "john_doe",
|
|
||||||
"role": "user",
|
|
||||||
"exp": 1737820800,
|
|
||||||
"iat": 1737216000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Безопасность
|
|
||||||
|
|
||||||
### Защита от атак
|
|
||||||
|
|
||||||
1. **CSRF (Cross-Site Request Forgery)**
|
|
||||||
- State parameter проверяется
|
|
||||||
- Случайное значение для каждого запроса
|
|
||||||
|
|
||||||
2. **Replay атаки**
|
|
||||||
- Nonce в id_token
|
|
||||||
- Одноразовое использование code
|
|
||||||
|
|
||||||
3. **Man-in-the-Middle**
|
|
||||||
- HTTPS обязателен в продакшене
|
|
||||||
- Проверка redirect_uri
|
|
||||||
|
|
||||||
4. **Token theft**
|
|
||||||
- JWT с истечением (7 дней)
|
|
||||||
- Хранение в localStorage (XSS защита нужна)
|
|
||||||
|
|
||||||
### Рекомендации для продакшена
|
|
||||||
|
|
||||||
```
|
|
||||||
✓ Использовать HTTPS
|
|
||||||
✓ Настроить CORS правильно
|
|
||||||
✓ Добавить rate limiting
|
|
||||||
✓ Логировать все входы
|
|
||||||
✓ Мониторить подозрительную активность
|
|
||||||
✓ Регулярно обновлять client_secret
|
|
||||||
✓ Использовать refresh tokens
|
|
||||||
```
|
|
||||||
|
|
||||||
## Готово! 🎉
|
|
||||||
|
|
||||||
Схема показывает полный цикл аутентификации через ZITADEL.
|
|
||||||
|
|
||||||
**Всё работает автоматически и безопасно!** 🔐
|
|
||||||
Reference in New Issue
Block a user