Files
ai-server/test_admin.py
Hyungi Ahn e102ce6db9 feat: AI 서버 관리 페이지 Phase 1 구현
- 웹 기반 관리 대시보드 추가 (/admin)
- 시스템 상태 모니터링 (AI 서버, Ollama, 활성 모델, API 호출)
- 모델 관리 기능 (목록 조회, 테스트, 새로고침)
- API 키 관리 시스템 (생성, 조회, 삭제)
- 반응형 UI/UX 디자인 (모바일 지원)
- 테스트 모드 서버 (test_admin.py) 추가
- 보안: API 키 기반 인증, 키 마스킹
- 실시간 업데이트 (30초 자동 새로고침)

구현 파일:
- templates/admin.html: 관리 페이지 HTML
- static/admin.css: 관리 페이지 스타일
- static/admin.js: 관리 페이지 JavaScript
- server/main.py: 관리 API 엔드포인트 추가
- test_admin.py: 맥북프로 테스트용 서버
- README.md: 관리 페이지 문서 업데이트
2025-08-18 13:33:39 +09:00

207 lines
6.3 KiB
Python

#!/usr/bin/env python3
"""
AI Server Admin Dashboard Test Server
맥북프로에서 관리 페이지만 테스트하기 위한 간단한 서버
"""
import os
import secrets
import uuid
from datetime import datetime
from pathlib import Path
from typing import Optional
from fastapi import FastAPI, Request, HTTPException, Depends, Header
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import uvicorn
# FastAPI 앱 초기화
app = FastAPI(title="AI Server Admin Dashboard (Test Mode)")
# 정적 파일 및 템플릿 설정
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
# 테스트용 설정
TEST_API_KEY = os.getenv("API_KEY", "test-admin-key-123")
TEST_SERVER_PORT = 28080
TEST_OLLAMA_HOST = "http://localhost:11434"
# 임시 데이터 저장소
api_keys_store = {
"test-key-1": {
"name": "Test Key 1",
"key": "test-api-key-abcd1234",
"created_at": datetime.now().isoformat(),
"usage_count": 42,
},
"test-key-2": {
"name": "Development Key",
"key": "dev-api-key-efgh5678",
"created_at": datetime.now().isoformat(),
"usage_count": 128,
}
}
# 테스트용 모델 데이터
test_models = [
{
"name": "llama3.2:3b",
"size": 2048000000, # 2GB
"status": "ready",
"is_active": True,
"last_used": datetime.now().isoformat(),
},
{
"name": "qwen2.5:7b",
"size": 4096000000, # 4GB
"status": "ready",
"is_active": False,
"last_used": "2024-12-20T10:30:00",
},
{
"name": "gemma2:2b",
"size": 1536000000, # 1.5GB
"status": "inactive",
"is_active": False,
"last_used": "2024-12-19T15:45:00",
}
]
def require_api_key(x_api_key: Optional[str] = Header(None), api_key: Optional[str] = None):
"""API 키 검증 (테스트 모드에서는 URL 파라미터도 허용)"""
# URL 파라미터로 API 키가 전달된 경우
if api_key and api_key == TEST_API_KEY:
return api_key
# 헤더로 API 키가 전달된 경우
if x_api_key and x_api_key == TEST_API_KEY:
return x_api_key
# 테스트 모드에서는 기본 허용
return "test-mode"
@app.get("/", response_class=HTMLResponse)
async def root():
"""루트 페이지 - 관리 페이지로 리다이렉트"""
return HTMLResponse("""
<html>
<head><title>AI Server Test</title></head>
<body>
<h1>AI Server Admin Dashboard (Test Mode)</h1>
<p>이것은 맥북프로에서 관리 페이지를 테스트하기 위한 서버입니다.</p>
<p><a href="/admin">관리 페이지로 이동</a></p>
<p><strong>API Key:</strong> <code>test-admin-key-123</code></p>
</body>
</html>
""")
@app.get("/health")
async def health_check():
"""헬스 체크"""
return {"status": "ok", "mode": "test", "timestamp": datetime.now().isoformat()}
# Admin Dashboard Routes
@app.get("/admin", response_class=HTMLResponse)
async def admin_dashboard(request: Request, api_key: str = Depends(require_api_key)):
"""관리자 대시보드 페이지"""
return templates.TemplateResponse("admin.html", {
"request": request,
"server_port": TEST_SERVER_PORT,
"ollama_host": TEST_OLLAMA_HOST,
})
@app.get("/admin/ollama/status")
async def admin_ollama_status(api_key: str = Depends(require_api_key)):
"""Ollama 서버 상태 확인 (테스트 모드)"""
return {"status": "offline", "error": "Test mode - Ollama not available"}
@app.get("/admin/models")
async def admin_get_models(api_key: str = Depends(require_api_key)):
"""설치된 모델 목록 조회 (테스트 데이터)"""
return {"models": test_models}
@app.get("/admin/models/active")
async def admin_get_active_model(api_key: str = Depends(require_api_key)):
"""현재 활성 모델 조회"""
active_model = next((m for m in test_models if m["is_active"]), None)
return {"model": active_model["name"] if active_model else "None"}
@app.post("/admin/models/test")
async def admin_test_model(request: dict, api_key: str = Depends(require_api_key)):
"""모델 테스트 (시뮬레이션)"""
model_name = request.get("model")
if not model_name:
raise HTTPException(status_code=400, detail="Model name is required")
# 테스트 모드에서는 시뮬레이션 결과 반환
return {
"result": f"Test mode simulation: Model '{model_name}' would respond with 'Hello! This is a test response from {model_name}.'"
}
@app.get("/admin/api-keys")
async def admin_get_api_keys(api_key: str = Depends(require_api_key)):
"""API 키 목록 조회"""
keys = []
for key_id, key_data in api_keys_store.items():
keys.append({
"id": key_id,
"name": key_data.get("name", "Unnamed"),
"key": key_data.get("key", ""),
"created_at": key_data.get("created_at", datetime.now().isoformat()),
"usage_count": key_data.get("usage_count", 0),
})
return {"api_keys": keys}
@app.post("/admin/api-keys")
async def admin_create_api_key(request: dict, api_key: str = Depends(require_api_key)):
"""새 API 키 생성"""
name = request.get("name", "Unnamed Key")
new_key = secrets.token_urlsafe(32)
key_id = str(uuid.uuid4())
api_keys_store[key_id] = {
"name": name,
"key": new_key,
"created_at": datetime.now().isoformat(),
"usage_count": 0,
}
return {"api_key": new_key, "key_id": key_id}
@app.delete("/admin/api-keys/{key_id}")
async def admin_delete_api_key(key_id: str, api_key: str = Depends(require_api_key)):
"""API 키 삭제"""
if key_id in api_keys_store:
del api_keys_store[key_id]
return {"message": "API key deleted successfully"}
else:
raise HTTPException(status_code=404, detail="API key not found")
if __name__ == "__main__":
print("🚀 AI Server Admin Dashboard (Test Mode)")
print(f"📍 Server: http://localhost:{TEST_SERVER_PORT}")
print(f"🔧 Admin: http://localhost:{TEST_SERVER_PORT}/admin")
print(f"🔑 API Key: {TEST_API_KEY}")
print("=" * 50)
uvicorn.run(
app,
host="0.0.0.0",
port=TEST_SERVER_PORT,
log_level="info"
)