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:
124
server/main.py
124
server/main.py
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user