|
|
|
|
|
from fastapi import APIRouter, Request, Depends, HTTPException, status |
|
|
from fastapi.responses import HTMLResponse, JSONResponse |
|
|
from fastapi.templating import Jinja2Templates |
|
|
from pydantic import BaseModel |
|
|
from typing import Dict, Any, List, Optional |
|
|
import psutil |
|
|
import asyncio |
|
|
from datetime import datetime |
|
|
|
|
|
from auth import get_current_active_user, User, Token, create_access_token |
|
|
from config import Config |
|
|
from proxy import opencode_proxy |
|
|
from version import get_version_info |
|
|
|
|
|
|
|
|
admin_router = APIRouter(prefix="/admin", tags=["管理"]) |
|
|
|
|
|
|
|
|
templates = Jinja2Templates(directory="templates") |
|
|
|
|
|
class SystemStatus(BaseModel): |
|
|
"""系统状态模型""" |
|
|
cpu_percent: float |
|
|
memory_percent: float |
|
|
disk_percent: float |
|
|
opencode_status: Dict[str, Any] |
|
|
fastapi_status: str |
|
|
uptime: str |
|
|
version_info: Dict[str, Any] |
|
|
|
|
|
class LoginRequest(BaseModel): |
|
|
"""登录请求模型""" |
|
|
username: str |
|
|
password: str |
|
|
|
|
|
class ServiceControl(BaseModel): |
|
|
"""服务控制模型""" |
|
|
action: str |
|
|
|
|
|
async def get_system_info() -> Dict[str, Any]: |
|
|
"""获取系统信息""" |
|
|
try: |
|
|
|
|
|
cpu_percent = psutil.cpu_percent(interval=1) |
|
|
|
|
|
|
|
|
memory = psutil.virtual_memory() |
|
|
memory_percent = memory.percent |
|
|
|
|
|
|
|
|
disk = psutil.disk_usage('/') |
|
|
disk_percent = (disk.used / disk.total) * 100 |
|
|
|
|
|
|
|
|
opencode_info = await opencode_proxy.get_service_info() |
|
|
|
|
|
|
|
|
boot_time = psutil.boot_time() |
|
|
uptime = datetime.now() - datetime.fromtimestamp(boot_time) |
|
|
uptime_str = str(uptime).split('.')[0] |
|
|
|
|
|
return { |
|
|
"cpu_percent": cpu_percent, |
|
|
"memory_percent": memory_percent, |
|
|
"disk_percent": disk_percent, |
|
|
"opencode_status": opencode_info, |
|
|
"fastapi_status": "healthy", |
|
|
"uptime": uptime_str |
|
|
} |
|
|
except Exception as e: |
|
|
return { |
|
|
"error": str(e), |
|
|
"cpu_percent": 0, |
|
|
"memory_percent": 0, |
|
|
"disk_percent": 0, |
|
|
"opencode_status": {"status": "unknown"}, |
|
|
"fastapi_status": "unknown", |
|
|
"uptime": "unknown" |
|
|
} |
|
|
|
|
|
@admin_router.get("/", response_class=HTMLResponse) |
|
|
async def admin_dashboard(request: Request): |
|
|
"""管理仪表板主页""" |
|
|
|
|
|
token = request.cookies.get("access_token") |
|
|
if token: |
|
|
try: |
|
|
|
|
|
return templates.TemplateResponse( |
|
|
"dashboard.html", |
|
|
{ |
|
|
"request": request, |
|
|
"title": "管理仪表板" |
|
|
} |
|
|
) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
return templates.TemplateResponse( |
|
|
"login.html", |
|
|
{ |
|
|
"request": request, |
|
|
"title": "管理员登录" |
|
|
} |
|
|
) |
|
|
|
|
|
@admin_router.get("/login", response_class=HTMLResponse) |
|
|
async def login_page(request: Request): |
|
|
"""登录页面""" |
|
|
return templates.TemplateResponse( |
|
|
"login.html", |
|
|
{ |
|
|
"request": request, |
|
|
"title": "管理员登录" |
|
|
} |
|
|
) |
|
|
|
|
|
@admin_router.post("/login") |
|
|
async def login(login_data: LoginRequest): |
|
|
"""处理登录""" |
|
|
if login_data.username != Config.ADMIN_USERNAME or login_data.password != Config.ADMIN_PASSWORD: |
|
|
raise HTTPException( |
|
|
status_code=status.HTTP_401_UNAUTHORIZED, |
|
|
detail="用户名或密码错误" |
|
|
) |
|
|
|
|
|
|
|
|
access_token_expires = datetime.timedelta(minutes=Config.JWT_EXPIRE_MINUTES) |
|
|
access_token = create_access_token( |
|
|
data={"sub": login_data.username}, |
|
|
expires_delta=access_token_expires |
|
|
) |
|
|
|
|
|
return { |
|
|
"access_token": access_token, |
|
|
"token_type": "bearer", |
|
|
"expires_in": Config.JWT_EXPIRE_MINUTES * 60 |
|
|
} |
|
|
|
|
|
@admin_router.get("/dashboard", response_class=HTMLResponse) |
|
|
async def dashboard(request: Request, current_user: User = Depends(get_current_active_user)): |
|
|
"""管理仪表板""" |
|
|
return templates.TemplateResponse( |
|
|
"dashboard.html", |
|
|
{ |
|
|
"request": request, |
|
|
"title": "管理仪表板", |
|
|
"user": current_user |
|
|
} |
|
|
) |
|
|
|
|
|
@admin_router.get("/api/system/status") |
|
|
async def get_system_status(current_user: User = Depends(get_current_active_user)) -> SystemStatus: |
|
|
"""获取系统状态""" |
|
|
system_info = await get_system_info() |
|
|
version_info = get_version_info() |
|
|
|
|
|
return SystemStatus( |
|
|
cpu_percent=system_info.get("cpu_percent", 0), |
|
|
memory_percent=system_info.get("memory_percent", 0), |
|
|
disk_percent=system_info.get("disk_percent", 0), |
|
|
opencode_status=system_info.get("opencode_status", {}), |
|
|
fastapi_status=system_info.get("fastapi_status", "unknown"), |
|
|
uptime=system_info.get("uptime", "unknown"), |
|
|
version_info=version_info |
|
|
) |
|
|
|
|
|
@admin_router.get("/api/version") |
|
|
async def get_version(current_user: User = Depends(get_current_active_user)) -> Dict[str, Any]: |
|
|
"""获取版本信息""" |
|
|
return get_version_info() |
|
|
|
|
|
@admin_router.post("/api/service/control") |
|
|
async def control_service( |
|
|
control: ServiceControl, |
|
|
current_user: User = Depends(get_current_active_user) |
|
|
): |
|
|
"""服务控制""" |
|
|
|
|
|
|
|
|
if control.action == "restart": |
|
|
|
|
|
return {"status": "success", "message": "服务重启成功"} |
|
|
elif control.action == "status": |
|
|
status_info = await opencode_proxy.get_service_info() |
|
|
return {"status": "success", "data": status_info} |
|
|
else: |
|
|
raise HTTPException( |
|
|
status_code=status.HTTP_400_BAD_REQUEST, |
|
|
detail="不支持的操作" |
|
|
) |
|
|
|
|
|
@admin_router.get("/api/logs") |
|
|
async def get_logs( |
|
|
lines: int = 100, |
|
|
current_user: User = Depends(get_current_active_user) |
|
|
) -> Dict[str, Any]: |
|
|
"""获取系统日志""" |
|
|
|
|
|
return { |
|
|
"logs": [ |
|
|
f"{datetime.now().isoformat()} - [INFO] - 服务运行正常", |
|
|
f"{datetime.now().isoformat()} - [INFO] - OpenCode 服务已启动", |
|
|
], |
|
|
"total_lines": 2 |
|
|
} |
|
|
|
|
|
@admin_router.post("/logout") |
|
|
async def logout(): |
|
|
"""登出""" |
|
|
return {"message": "登出成功"} |