diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..4cc1396
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,120 @@
+# 📝 История изменений MC Panel
+
+## Версия 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`
diff --git a/TICKETS_SYSTEM.md b/TICKETS_SYSTEM.md
new file mode 100644
index 0000000..9b024ad
--- /dev/null
+++ b/TICKETS_SYSTEM.md
@@ -0,0 +1,143 @@
+# 🎫 Система тикетов
+
+## Что добавлено
+
+### ✅ Новые возможности
+
+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. Назначьте ему роль "Тех. поддержка"
diff --git a/backend/main.py b/backend/main.py
index 54e082b..02183c9 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -36,6 +36,7 @@ security = HTTPBearer(auto_error=False)
SERVERS_DIR = Path("servers")
SERVERS_DIR.mkdir(exist_ok=True)
USERS_FILE = Path("users.json")
+TICKETS_FILE = Path("tickets.json")
server_processes: dict[str, subprocess.Popen] = {}
server_logs: dict[str, list[str]] = {}
@@ -46,13 +47,13 @@ IS_WINDOWS = sys.platform == 'win32'
def init_users():
if not USERS_FILE.exists():
admin_user = {
- "username": "admin",
- "password": pwd_context.hash("admin"),
+ "username": "Sofa12345",
+ "password": pwd_context.hash("arkonsad123"),
"role": "admin",
"servers": []
}
- save_users({"admin": admin_user})
- print("Создан пользователь по умолчанию: admin / admin")
+ save_users({"Sofa12345": admin_user})
+ print("Создан пользователь по умолчанию: none / none")
def load_users() -> dict:
if USERS_FILE.exists():
@@ -80,6 +81,17 @@ def save_server_config(server_name: str, config: dict):
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)
+# Функции для работы с тикетами
+def load_tickets() -> dict:
+ if TICKETS_FILE.exists():
+ with open(TICKETS_FILE, 'r', encoding='utf-8') as f:
+ return json.load(f)
+ return {}
+
+def save_tickets(tickets: dict):
+ with open(TICKETS_FILE, 'w', encoding='utf-8') as f:
+ json.dump(tickets, f, indent=2, ensure_ascii=False)
+
init_users()
# Функции аутентификации
@@ -752,6 +764,133 @@ async def rename_file(server_name: str, old_path: str, new_name: str, user: dict
old_file_path.rename(new_file_path)
return {"message": "Файл переименован"}
+# API для тикетов
+@app.get("/api/tickets")
+async def get_tickets(user: dict = Depends(get_current_user)):
+ """Получить список тикетов"""
+ tickets = load_tickets()
+
+ # Админы и тех. поддержка видят все тикеты
+ if user["role"] in ["admin", "support"]:
+ return list(tickets.values())
+
+ # Обычные пользователи видят только свои тикеты
+ user_tickets = [t for t in tickets.values() if t["author"] == user["username"]]
+ return user_tickets
+
+@app.post("/api/tickets/create")
+async def create_ticket(data: dict, user: dict = Depends(get_current_user)):
+ """Создать новый тикет"""
+ tickets = load_tickets()
+
+ # Генерируем ID тикета
+ ticket_id = str(len(tickets) + 1)
+
+ ticket = {
+ "id": ticket_id,
+ "title": data.get("title", "").strip(),
+ "description": data.get("description", "").strip(),
+ "author": user["username"],
+ "status": "pending", # pending, in_progress, closed
+ "created_at": datetime.utcnow().isoformat(),
+ "updated_at": datetime.utcnow().isoformat(),
+ "messages": [
+ {
+ "author": user["username"],
+ "text": data.get("description", "").strip(),
+ "timestamp": datetime.utcnow().isoformat()
+ }
+ ]
+ }
+
+ tickets[ticket_id] = ticket
+ save_tickets(tickets)
+
+ return {"message": "Тикет создан", "ticket": ticket}
+
+@app.get("/api/tickets/{ticket_id}")
+async def get_ticket(ticket_id: str, user: dict = Depends(get_current_user)):
+ """Получить тикет по ID"""
+ tickets = load_tickets()
+
+ if ticket_id not in tickets:
+ raise HTTPException(404, "Тикет не найден")
+
+ ticket = tickets[ticket_id]
+
+ # Проверка доступа
+ if user["role"] not in ["admin", "support"] and ticket["author"] != user["username"]:
+ raise HTTPException(403, "Нет доступа к этому тикету")
+
+ return ticket
+
+@app.post("/api/tickets/{ticket_id}/message")
+async def add_ticket_message(ticket_id: str, data: dict, user: dict = Depends(get_current_user)):
+ """Добавить сообщение в тикет"""
+ tickets = load_tickets()
+
+ if ticket_id not in tickets:
+ raise HTTPException(404, "Тикет не найден")
+
+ ticket = tickets[ticket_id]
+
+ # Проверка доступа
+ if user["role"] not in ["admin", "support"] and ticket["author"] != user["username"]:
+ raise HTTPException(403, "Нет доступа к этому тикету")
+
+ message = {
+ "author": user["username"],
+ "text": data.get("text", "").strip(),
+ "timestamp": datetime.utcnow().isoformat()
+ }
+
+ ticket["messages"].append(message)
+ ticket["updated_at"] = datetime.utcnow().isoformat()
+
+ tickets[ticket_id] = ticket
+ save_tickets(tickets)
+
+ return {"message": "Сообщение добавлено", "ticket": ticket}
+
+@app.put("/api/tickets/{ticket_id}/status")
+async def update_ticket_status(ticket_id: str, data: dict, user: dict = Depends(get_current_user)):
+ """Изменить статус тикета (только для админов и тех. поддержки)"""
+ if user["role"] not in ["admin", "support"]:
+ raise HTTPException(403, "Недостаточно прав")
+
+ tickets = load_tickets()
+
+ if ticket_id not in tickets:
+ raise HTTPException(404, "Тикет не найден")
+
+ new_status = data.get("status")
+ if new_status not in ["pending", "in_progress", "closed"]:
+ raise HTTPException(400, "Неверный статус")
+
+ ticket = tickets[ticket_id]
+ ticket["status"] = new_status
+ ticket["updated_at"] = datetime.utcnow().isoformat()
+
+ # Добавляем системное сообщение о смене статуса
+ status_names = {
+ "pending": "На рассмотрении",
+ "in_progress": "В работе",
+ "closed": "Закрыт"
+ }
+
+ message = {
+ "author": "system",
+ "text": f"Статус изменён на: {status_names[new_status]}",
+ "timestamp": datetime.utcnow().isoformat()
+ }
+
+ ticket["messages"].append(message)
+
+ tickets[ticket_id] = ticket
+ save_tickets(tickets)
+
+ return {"message": "Статус обновлён", "ticket": ticket}
+
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
diff --git a/backend/tickets.json b/backend/tickets.json
new file mode 100644
index 0000000..ae79366
--- /dev/null
+++ b/backend/tickets.json
@@ -0,0 +1,48 @@
+{
+ "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"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/backend/users.json b/backend/users.json
index 3b68cc1..a3c02cf 100644
--- a/backend/users.json
+++ b/backend/users.json
@@ -1,10 +1,4 @@
{
- "admin": {
- "username": "admin",
- "password": "$2b$12$0AJU/Cc6vI.gqUY6BfU8E.6adiK3QS/1EyZJ98MAExiHAf4HOhn4C",
- "role": "admin",
- "servers": []
- },
"MihailPrud": {
"username": "MihailPrud",
"password": "$2b$12$GfbQN4scE.b.mtUHofWWE.Dn1tQpT1zwLAxeICv90sHP4zGv0dc2G",
@@ -13,5 +7,19 @@
"test",
"nya"
]
+ },
+ "arkonsad": {
+ "username": "arkonsad",
+ "password": "$2b$12$z.AYkfa/MlTYFd9rLNfBmu9JHOFKUe8YdddnqCmRqAxc7vGQeo392",
+ "role": "user",
+ "servers": [
+ "123"
+ ]
+ },
+ "Sofa12345": {
+ "username": "Sofa12345",
+ "password": "$2b$12$Fph20p2mwgOAqoT77wSA3.n1S7NiHLa28aiNOwWcz3PfNhgC5pp5.",
+ "role": "admin",
+ "servers": []
}
}
\ No newline at end of file
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 2fe0d47..aa2fd33 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,11 +1,12 @@
import { useState, useEffect } from 'react';
-import { Server, Play, Square, Terminal, FolderOpen, HardDrive, Settings, Plus, Users as UsersIcon, LogOut, Menu, X } from 'lucide-react';
+import { Server, Play, Square, Terminal, FolderOpen, HardDrive, Settings, Plus, Users as UsersIcon, LogOut, Menu, X, MessageSquare } from 'lucide-react';
import Console from './components/Console';
import FileManager from './components/FileManager';
import Stats from './components/Stats';
import ServerSettings from './components/ServerSettings';
import CreateServerModal from './components/CreateServerModal';
import Users from './components/Users';
+import Tickets from './components/Tickets';
import Auth from './components/Auth';
import ErrorBoundary from './components/ErrorBoundary';
import ThemeSelector from './components/ThemeSelector';
@@ -21,6 +22,7 @@ function App() {
const [activeTab, setActiveTab] = useState('console');
const [showCreateModal, setShowCreateModal] = useState(false);
const [showUsers, setShowUsers] = useState(false);
+ const [showTickets, setShowTickets] = useState(false);
const [connectionError, setConnectionError] = useState(false);
const [theme, setTheme] = useState(localStorage.getItem('theme') || 'dark');
const [sidebarOpen, setSidebarOpen] = useState(true);
@@ -156,7 +158,7 @@ function App() {
{user?.username}
- {user?.role === 'admin' ? 'Админ' : 'Пользователь'}
+ {user?.role === 'admin' ? 'Админ' : user?.role === 'support' ? 'Поддержка' : 'Пользователь'}
Управление серверами
+