""" Управление демонами (удаленными серверами) """ from fastapi import APIRouter, HTTPException, Depends, Header from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from pydantic import BaseModel from typing import List, Optional import json import httpx from pathlib import Path from jose import JWTError, jwt router = APIRouter() security = HTTPBearer(auto_error=False) # Файл с конфигурацией демонов DAEMONS_FILE = Path("data/daemons.json") DAEMONS_FILE.parent.mkdir(exist_ok=True) # Файл с пользователями - проверяем оба возможных пути if Path("users.json").exists(): USERS_FILE = Path("users.json") elif Path("backend/users.json").exists(): USERS_FILE = Path("backend/users.json") else: USERS_FILE = Path("users.json") # По умолчанию # Настройки JWT (должны совпадать с main.py) SECRET_KEY = "your-secret-key-change-this-in-production-12345" ALGORITHM = "HS256" def load_users_dict(): """Загрузить пользователей из файла""" if USERS_FILE.exists(): with open(USERS_FILE, 'r', encoding='utf-8') as f: return json.load(f) return {} def get_current_user_from_token(credentials: HTTPAuthorizationCredentials = Depends(security)): """Получить текущего пользователя из токена""" if not credentials: raise HTTPException(status_code=401, detail="Требуется авторизация") token = credentials.credentials try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise HTTPException(status_code=401, detail="Неверный токен") # Пытаемся получить роль из токена role = payload.get("role") print(f"[DEBUG] Username from token: {username}") print(f"[DEBUG] Role from token: {role}") # Если роли нет в токене, загружаем из базы if not role: print(f"[DEBUG] Role not in token, loading from database...") print(f"[DEBUG] USERS_FILE path: {USERS_FILE}") print(f"[DEBUG] USERS_FILE exists: {USERS_FILE.exists()}") users = load_users_dict() print(f"[DEBUG] Loaded users: {list(users.keys())}") if username not in users: raise HTTPException(status_code=401, detail="Пользователь не найден") role = users[username].get("role", "user") print(f"[DEBUG] Role from database: {role}") print(f"[DEBUG] Final role: {role}") return {"username": username, "role": role} except JWTError as e: print(f"[DEBUG] JWT Error: {e}") raise HTTPException(status_code=401, detail="Неверный токен") class DaemonCreate(BaseModel): name: str address: str port: int key: str remarks: Optional[str] = "" class DaemonUpdate(BaseModel): name: Optional[str] = None address: Optional[str] = None port: Optional[int] = None key: Optional[str] = None remarks: Optional[str] = None def load_daemons(): """Загрузить список демонов""" if not DAEMONS_FILE.exists(): return {} with open(DAEMONS_FILE, 'r', encoding='utf-8') as f: return json.load(f) def save_daemons(daemons: dict): """Сохранить список демонов""" with open(DAEMONS_FILE, 'w', encoding='utf-8') as f: json.dump(daemons, f, indent=2, ensure_ascii=False) async def check_daemon_connection(address: str, port: int, key: str) -> dict: """Проверить подключение к демону""" url = f"http://{address}:{port}/api/status" headers = {"Authorization": f"Bearer {key}"} print(f"[DEBUG] Checking daemon connection:") print(f"[DEBUG] URL: {url}") print(f"[DEBUG] Key: {key[:20]}...") try: async with httpx.AsyncClient(timeout=5.0) as client: response = await client.get(url, headers=headers) print(f"[DEBUG] Status: {response.status_code}") if response.status_code == 200: data = response.json() print(f"[DEBUG] Response: {data}") return data else: print(f"[DEBUG] Error response: {response.text}") raise HTTPException(status_code=400, detail=f"Failed to connect to daemon: {response.status_code}") except httpx.RequestError as e: print(f"[DEBUG] Connection error: {e}") raise HTTPException(status_code=400, detail=f"Connection error: {str(e)}") @router.get("/api/daemons") async def get_daemons(current_user: dict = Depends(get_current_user_from_token)): """Получить список всех демонов""" # Только админы и владельцы могут видеть демоны if current_user["role"] not in ["owner", "admin"]: raise HTTPException(status_code=403, detail="Access denied") daemons = load_daemons() # Проверяем статус каждого демона result = [] for daemon_id, daemon in daemons.items(): daemon_info = { "id": daemon_id, **daemon, "status": "offline" } try: # Пытаемся получить статус status = await check_daemon_connection( daemon["address"], daemon["port"], daemon["key"] ) daemon_info["status"] = "online" daemon_info["system"] = status.get("system", {}) daemon_info["servers"] = status.get("servers", {}) except: pass result.append(daemon_info) return result @router.post("/api/daemons") async def create_daemon( daemon: DaemonCreate, current_user: dict = Depends(get_current_user_from_token) ): """Добавить новый демон""" if current_user["role"] not in ["owner", "admin"]: raise HTTPException(status_code=403, detail="Access denied") # Проверяем подключение await check_daemon_connection(daemon.address, daemon.port, daemon.key) daemons = load_daemons() # Генерируем ID daemon_id = f"daemon-{len(daemons) + 1}" daemons[daemon_id] = { "name": daemon.name, "address": daemon.address, "port": daemon.port, "key": daemon.key, "remarks": daemon.remarks, "created_at": str(Path().cwd()) # Временная метка } save_daemons(daemons) return { "success": True, "daemon_id": daemon_id, "message": "Daemon added successfully" } @router.get("/api/daemons/{daemon_id}") async def get_daemon( daemon_id: str, current_user: dict = Depends(get_current_user_from_token) ): """Получить информацию о демоне""" if current_user["role"] not in ["owner", "admin"]: raise HTTPException(status_code=403, detail="Access denied") daemons = load_daemons() if daemon_id not in daemons: raise HTTPException(status_code=404, detail="Daemon not found") daemon = daemons[daemon_id] # Получаем статус try: status = await check_daemon_connection( daemon["address"], daemon["port"], daemon["key"] ) daemon["status"] = "online" daemon["system"] = status.get("system", {}) daemon["servers"] = status.get("servers", {}) except: daemon["status"] = "offline" return { "id": daemon_id, **daemon } @router.put("/api/daemons/{daemon_id}") async def update_daemon( daemon_id: str, daemon_update: DaemonUpdate, current_user: dict = Depends(get_current_user_from_token) ): """Обновить демон""" if current_user["role"] not in ["owner", "admin"]: raise HTTPException(status_code=403, detail="Access denied") daemons = load_daemons() if daemon_id not in daemons: raise HTTPException(status_code=404, detail="Daemon not found") # Обновляем поля daemon = daemons[daemon_id] if daemon_update.name: daemon["name"] = daemon_update.name if daemon_update.address: daemon["address"] = daemon_update.address if daemon_update.port: daemon["port"] = daemon_update.port if daemon_update.key: daemon["key"] = daemon_update.key if daemon_update.remarks is not None: daemon["remarks"] = daemon_update.remarks # Проверяем подключение с новыми данными await check_daemon_connection( daemon["address"], daemon["port"], daemon["key"] ) save_daemons(daemons) return { "success": True, "message": "Daemon updated successfully" } @router.delete("/api/daemons/{daemon_id}") async def delete_daemon( daemon_id: str, current_user: dict = Depends(get_current_user_from_token) ): """Удалить демон""" if current_user["role"] not in ["owner", "admin"]: raise HTTPException(status_code=403, detail="Access denied") daemons = load_daemons() if daemon_id not in daemons: raise HTTPException(status_code=404, detail="Daemon not found") del daemons[daemon_id] save_daemons(daemons) return { "success": True, "message": "Daemon deleted successfully" } @router.get("/api/daemons/{daemon_id}/servers") async def get_daemon_servers( daemon_id: str, current_user: dict = Depends(get_current_user_from_token) ): """Получить список серверов на демоне""" daemons = load_daemons() if daemon_id not in daemons: raise HTTPException(status_code=404, detail="Daemon not found") daemon = daemons[daemon_id] url = f"http://{daemon['address']}:{daemon['port']}/api/servers" headers = {"Authorization": f"Bearer {daemon['key']}"} try: async with httpx.AsyncClient(timeout=10.0) as client: response = await client.get(url, headers=headers) if response.status_code == 200: return response.json() else: raise HTTPException(status_code=400, detail="Failed to get servers from daemon") except httpx.RequestError as e: raise HTTPException(status_code=400, detail=f"Connection error: {str(e)}")