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: 관리 페이지 문서 업데이트
This commit is contained in:
Hyungi Ahn
2025-08-18 13:33:39 +09:00
parent cb009f7393
commit e102ce6db9
6 changed files with 1142 additions and 4 deletions

View File

@@ -471,3 +471,127 @@ async def chat_page(request: Request):
"api_key": os.getenv("API_KEY", "")
})
# 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": settings.ai_server_port,
"ollama_host": settings.ollama_host,
})
@app.get("/admin/ollama/status")
async def admin_ollama_status(api_key: str = Depends(require_api_key)):
"""Ollama 서버 상태 확인"""
try:
# Ollama 서버에 ping 요청
response = await ollama.client.get(f"{settings.ollama_host}/api/tags")
if response.status_code == 200:
return {"status": "online", "models_count": len(response.json().get("models", []))}
else:
return {"status": "offline", "error": f"HTTP {response.status_code}"}
except Exception as e:
return {"status": "offline", "error": str(e)}
@app.get("/admin/models")
async def admin_get_models(api_key: str = Depends(require_api_key)):
"""설치된 모델 목록 조회"""
try:
models_data = await ollama.list_models()
models = []
for model in models_data.get("models", []):
models.append({
"name": model.get("name", "Unknown"),
"size": model.get("size", 0),
"status": "ready",
"is_active": model.get("name") == settings.base_model,
"last_used": model.get("modified_at"),
})
return {"models": models}
except Exception as e:
return {"models": [], "error": str(e)}
@app.get("/admin/models/active")
async def admin_get_active_model(api_key: str = Depends(require_api_key)):
"""현재 활성 모델 조회"""
return {"model": settings.base_model}
@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")
try:
# 간단한 테스트 메시지 전송
test_response = await ollama.generate(
model=model_name,
prompt="Hello, this is a test. Please respond with 'Test successful'.",
stream=False
)
return {
"result": f"Test successful. Model responded: {test_response.get('response', 'No response')[:100]}..."
}
except Exception as e:
return {"result": f"Test failed: {str(e)}"}
# API Key Management (Placeholder - 실제 구현은 데이터베이스 필요)
api_keys_store = {} # 임시 저장소
@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 키 생성"""
import secrets
import uuid
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")