Files
wiseclaw/backend/app/admin/routes.py

256 lines
9.1 KiB
Python

from pathlib import Path
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.admin.services import AdminService
from app.config import get_settings as get_app_settings
from app.db import SecretORM, get_session
from app.google.auth import GoogleAuthError, GoogleAuthManager
from app.llm.ollama_client import OllamaClient
from app.models import (
AnythingLLMStatus,
AutomationRecord,
GoogleIntegrationStatus,
MemoryRecord,
OllamaStatus,
RuntimeSettings,
TelegramStatus,
UserProfileRecord,
UserRecord,
)
from app.tools.second_brain import SecondBrainTool
router = APIRouter(prefix="/admin", tags=["admin"])
class SecretPayload(BaseModel):
key: str
value: str
class GoogleClientPayload(BaseModel):
client_id: str
client_secret: str
def get_admin_service(session: Session = Depends(get_session)) -> AdminService:
return AdminService(session)
def get_google_auth_manager() -> GoogleAuthManager:
return GoogleAuthManager(get_app_settings(), Path(__file__).resolve().parents[2])
def sync_google_client_file(service: AdminService, manager: GoogleAuthManager) -> None:
settings = get_app_settings()
client_id_record = service.session.get(SecretORM, "google_client_id")
client_secret_record = service.session.get(SecretORM, "google_client_secret")
client_id = (client_id_record.value if client_id_record else settings.google_client_id).strip()
client_secret = (client_secret_record.value if client_secret_record else settings.google_client_secret).strip()
if client_id and client_secret:
manager.write_client_secrets_file(client_id, client_secret)
@router.get("/dashboard")
def get_dashboard(service: AdminService = Depends(get_admin_service)):
return service.dashboard()
@router.get("/settings", response_model=RuntimeSettings)
def get_runtime_settings(service: AdminService = Depends(get_admin_service)):
return service.get_runtime_settings()
@router.put("/settings", response_model=RuntimeSettings)
def put_settings(payload: RuntimeSettings, service: AdminService = Depends(get_admin_service)):
return service.update_runtime_settings(payload)
@router.get("/users", response_model=list[UserRecord])
def get_users(service: AdminService = Depends(get_admin_service)):
return service.list_users()
@router.post("/users", response_model=UserRecord)
def post_user(payload: UserRecord, service: AdminService = Depends(get_admin_service)):
return service.save_user(payload)
@router.get("/profiles", response_model=list[UserProfileRecord])
def get_profiles(service: AdminService = Depends(get_admin_service)):
return service.list_user_profiles()
@router.get("/automations", response_model=list[AutomationRecord])
def get_automations(service: AdminService = Depends(get_admin_service)):
return service.list_automations()
@router.get("/memory", response_model=list[MemoryRecord])
def get_memory(service: AdminService = Depends(get_admin_service)):
return service.list_memory()
@router.delete("/memory")
def delete_memory(service: AdminService = Depends(get_admin_service)):
service.clear_memory()
return {"status": "ok"}
@router.get("/secrets/{key}")
def get_secret(key: str, service: AdminService = Depends(get_admin_service)):
return {"key": key, "masked": service.get_secret_mask(key)}
@router.post("/secrets")
def post_secret(payload: SecretPayload, service: AdminService = Depends(get_admin_service)):
service.save_secret(payload.key, payload.value)
return {"status": "ok"}
@router.post("/integrations/google/client")
def post_google_client(
payload: GoogleClientPayload,
service: AdminService = Depends(get_admin_service),
manager: GoogleAuthManager = Depends(get_google_auth_manager),
):
service.save_secret("google_client_id", payload.client_id.strip())
service.save_secret("google_client_secret", payload.client_secret.strip())
sync_google_client_file(service, manager)
return {"status": "ok"}
@router.get("/integrations/llm", response_model=OllamaStatus)
@router.get("/integrations/ollama", response_model=OllamaStatus)
async def get_llm_status(service: AdminService = Depends(get_admin_service)):
runtime = service.get_runtime_settings()
settings = get_app_settings()
secret = service.session.get(SecretORM, "zai_api_key") if runtime.model_provider == "zai" else None
client = OllamaClient(
base_url=runtime.local_base_url if runtime.model_provider == "local" else settings.zai_base_url,
provider=runtime.model_provider,
api_key=secret.value if secret else settings.zai_api_key,
)
return await client.status(runtime.local_model if runtime.model_provider == "local" else runtime.zai_model)
@router.get("/integrations/telegram", response_model=TelegramStatus)
def get_telegram_status(service: AdminService = Depends(get_admin_service)):
return service.telegram_status()
@router.get("/integrations/anythingllm", response_model=AnythingLLMStatus)
async def get_anythingllm_status(service: AdminService = Depends(get_admin_service)):
runtime = service.get_runtime_settings()
settings = get_app_settings()
secret = service.session.get(SecretORM, "anythingllm_api_key")
tool = SecondBrainTool(
base_url=runtime.anythingllm_base_url,
workspace_slug=runtime.anythingllm_workspace_slug,
api_key=secret.value if secret else settings.anythingllm_api_key,
)
status = await tool.status()
return AnythingLLMStatus(
reachable=bool(status.get("reachable")),
workspace_found=bool(status.get("workspace_found")),
base_url=runtime.anythingllm_base_url,
workspace_slug=runtime.anythingllm_workspace_slug,
message=str(status.get("message", "")),
)
@router.get("/integrations/google", response_model=GoogleIntegrationStatus)
def get_google_status(
request: Request,
service: AdminService = Depends(get_admin_service),
manager: GoogleAuthManager = Depends(get_google_auth_manager),
):
sync_google_client_file(service, manager)
client_configured, connected, message = manager.oauth_status()
connect_url = str(request.url_for("google_oauth_connect"))
return GoogleIntegrationStatus(
client_configured=client_configured,
connected=connected,
connect_url=connect_url,
message=message,
)
@router.get("/integrations/google/connect", name="google_oauth_connect")
def google_oauth_connect(
request: Request,
service: AdminService = Depends(get_admin_service),
manager: GoogleAuthManager = Depends(get_google_auth_manager),
):
redirect_uri = str(request.url_for("google_oauth_callback"))
try:
sync_google_client_file(service, manager)
authorization_url = manager.begin_web_oauth(redirect_uri)
except GoogleAuthError as exc:
return HTMLResponse(
(
"<html><body style='font-family: sans-serif; padding: 24px;'>"
f"<h2>Google connect failed</h2><p>{exc}</p>"
"<p>Add your client_secret.json file, then try the connect button again.</p>"
"</body></html>"
),
status_code=400,
)
return RedirectResponse(url=authorization_url)
@router.get("/integrations/google/callback", response_class=HTMLResponse, name="google_oauth_callback")
def google_oauth_callback(
request: Request,
state: str | None = None,
error: str | None = None,
manager: GoogleAuthManager = Depends(get_google_auth_manager),
):
if error:
return HTMLResponse(
(
"<html><body style='font-family: sans-serif; padding: 24px;'>"
f"<h2>Google connect failed</h2><p>{error}</p>"
"<p>You can close this tab and try again from the WiseClaw admin panel.</p>"
"</body></html>"
),
status_code=400,
)
if not state:
return HTMLResponse(
(
"<html><body style='font-family: sans-serif; padding: 24px;'>"
"<h2>Google connect failed</h2><p>Missing OAuth state.</p>"
"<p>You can close this tab and try again from the WiseClaw admin panel.</p>"
"</body></html>"
),
status_code=400,
)
try:
manager.complete_web_oauth(str(request.url_for("google_oauth_callback")), state, str(request.url))
except Exception as exc:
return HTMLResponse(
(
"<html><body style='font-family: sans-serif; padding: 24px;'>"
f"<h2>Google connect failed</h2><p>{exc}</p>"
"<p>You can close this tab and try again from the WiseClaw admin panel.</p>"
"</body></html>"
),
status_code=400,
)
return HTMLResponse(
(
"<html><body style='font-family: sans-serif; padding: 24px;'>"
"<h2>Google account connected</h2>"
"<p>WiseClaw can now use your Gmail and Google Drive tools.</p>"
"<p>You can close this tab and refresh the admin panel.</p>"
"</body></html>"
)
)