fix: delete agent 500 error + dynamic personality + deployment guide
- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions, schedules, executions, team_members) and unbind goals/tasks before delete - Remove hardcoded personality templates in Android, replace with dynamic system prompt generation from name + description - Set promptSectionsEnabled=false to bypass PromptComposer for personality - Add Tencent Cloud Linux deployment guide (Docker Compose) - Accumulated backend service updates, frontend UI fixes, Android app changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
125
backend/app/api/audit_logs.py
Normal file
125
backend/app/api/audit_logs.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
操作审计日志 API
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, desc
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.api.auth import get_current_user
|
||||
from app.models.user import User
|
||||
from app.models.audit_log import AuditLog
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/v1/audit-logs",
|
||||
tags=["audit-logs"],
|
||||
responses={
|
||||
401: {"description": "未授权"},
|
||||
403: {"description": "无权访问"},
|
||||
500: {"description": "服务器内部错误"}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ── Pydantic Schemas ─────────────────────────────────────────────
|
||||
|
||||
class AuditLogItem(BaseModel):
|
||||
id: str
|
||||
username: str
|
||||
action: str
|
||||
resource_type: str
|
||||
resource_id: Optional[str] = None
|
||||
resource_name: Optional[str] = None
|
||||
detail: Optional[dict] = None
|
||||
ip_address: Optional[str] = None
|
||||
status: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class AuditLogStats(BaseModel):
|
||||
total: int
|
||||
by_action: dict # {"CREATE": 10, "UPDATE": 5, ...}
|
||||
by_resource: dict # {"agent": 8, "workflow": 7, ...}
|
||||
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────
|
||||
|
||||
def _check_admin(current_user: User):
|
||||
if getattr(current_user, "role", None) != "admin":
|
||||
from app.core.exceptions import ForbiddenError
|
||||
raise ForbiddenError("仅管理员可访问审计日志")
|
||||
|
||||
|
||||
# ── Endpoints ────────────────────────────────────────────────────
|
||||
|
||||
@router.get("", response_model=List[AuditLogItem])
|
||||
async def get_audit_logs(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
user_id: Optional[str] = Query(None, description="按用户ID过滤"),
|
||||
action: Optional[str] = Query(None, description="操作类型: CREATE/UPDATE/DELETE/EXECUTE/LOGIN"),
|
||||
resource_type: Optional[str] = Query(None, description="资源类型: agent/workflow/user"),
|
||||
status: Optional[str] = Query(None, description="操作状态: success/failure"),
|
||||
start_date: Optional[str] = Query(None, description="开始时间 ISO格式"),
|
||||
end_date: Optional[str] = Query(None, description="结束时间 ISO格式"),
|
||||
keyword: Optional[str] = Query(None, description="资源名称关键词搜索"),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=1000),
|
||||
):
|
||||
"""查询操作审计日志(仅管理员)"""
|
||||
_check_admin(current_user)
|
||||
|
||||
query = db.query(AuditLog)
|
||||
|
||||
if user_id:
|
||||
query = query.filter(AuditLog.user_id == user_id)
|
||||
if action:
|
||||
query = query.filter(AuditLog.action == action.upper())
|
||||
if resource_type:
|
||||
query = query.filter(AuditLog.resource_type == resource_type)
|
||||
if status:
|
||||
query = query.filter(AuditLog.status == status)
|
||||
if keyword:
|
||||
query = query.filter(AuditLog.resource_name.contains(keyword))
|
||||
if start_date:
|
||||
query = query.filter(AuditLog.created_at >= datetime.fromisoformat(start_date))
|
||||
if end_date:
|
||||
query = query.filter(AuditLog.created_at <= datetime.fromisoformat(end_date))
|
||||
|
||||
total = query.count()
|
||||
logs = query.order_by(desc(AuditLog.created_at)).offset(skip).limit(limit).all()
|
||||
|
||||
return logs
|
||||
|
||||
|
||||
@router.get("/stats", response_model=AuditLogStats)
|
||||
async def get_audit_logs_stats(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""获取审计日志统计(仅管理员)"""
|
||||
_check_admin(current_user)
|
||||
|
||||
total = db.query(func.count(AuditLog.id)).scalar() or 0
|
||||
|
||||
action_rows = db.query(
|
||||
AuditLog.action, func.count(AuditLog.id)
|
||||
).group_by(AuditLog.action).all()
|
||||
by_action = {row[0]: row[1] for row in action_rows}
|
||||
|
||||
resource_rows = db.query(
|
||||
AuditLog.resource_type, func.count(AuditLog.id)
|
||||
).group_by(AuditLog.resource_type).all()
|
||||
by_resource = {row[0]: row[1] for row in resource_rows}
|
||||
|
||||
return {"total": total, "by_action": by_action, "by_resource": by_resource}
|
||||
Reference in New Issue
Block a user