重试队列增强 + 审计分页
- 后端: 添加 POST /api/retries/{id}/retry (手动重试) + DELETE /api/retries/{id}
+ 审计日志分页(offset/limit/count) + PushRetryJobRepo新增get_all/get_by_id/delete
- 前端retries: 手动重试按钮、删除按钮、状态过滤、操作人显示、toast反馈
- 前端audit: 分页控件(上一页/下一页)、每页50条、新增retry_job/delete_retry过滤
- 修复index.html: 适配审计API新返回格式(data.items)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+73
-8
@@ -239,20 +239,27 @@ async def delete_preset(
|
||||
audit_router = APIRouter(prefix="/api/audit", tags=["audit"])
|
||||
|
||||
|
||||
@audit_router.get("/", response_model=list)
|
||||
@audit_router.get("/", response_model=dict)
|
||||
async def list_audit_logs(
|
||||
action: Optional[str] = None,
|
||||
limit: int = 200,
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
admin: Admin = Depends(get_current_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""List recent audit logs, optionally filtered by action"""
|
||||
"""List audit logs with pagination"""
|
||||
repo = AuditLogRepositoryImpl(db)
|
||||
total = await repo.count(action)
|
||||
if action:
|
||||
logs = await repo.get_by_action(action, limit)
|
||||
logs = await repo.get_by_action(action, limit, offset)
|
||||
else:
|
||||
logs = await repo.get_recent(limit)
|
||||
return [_audit_to_dict(log) for log in logs]
|
||||
logs = await repo.get_recent(limit, offset)
|
||||
return {
|
||||
"total": total,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"items": [_audit_to_dict(log) for log in logs],
|
||||
}
|
||||
|
||||
|
||||
# ── Retry Jobs ──
|
||||
@@ -266,12 +273,70 @@ async def list_retry_jobs(
|
||||
admin: Admin = Depends(get_current_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""List pending retry jobs"""
|
||||
"""List all retry jobs (not just pending)"""
|
||||
repo = PushRetryJobRepositoryImpl(db)
|
||||
jobs = await repo.get_pending(limit)
|
||||
jobs = await repo.get_all(limit)
|
||||
return [_retry_to_dict(j) for j in jobs]
|
||||
|
||||
|
||||
@retry_router.post("/{id}/retry", response_model=dict)
|
||||
async def retry_job(
|
||||
id: int,
|
||||
admin: Admin = Depends(get_current_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Manually retry a failed/pending job"""
|
||||
repo = PushRetryJobRepositoryImpl(db)
|
||||
job = await repo.get_by_id(id)
|
||||
if not job:
|
||||
raise HTTPException(status_code=404, detail="Retry job not found")
|
||||
|
||||
# Reset the job to pending state for immediate retry
|
||||
job = await repo.update_status(
|
||||
id, "pending",
|
||||
retry_count=0,
|
||||
next_retry_at=None,
|
||||
last_error=None,
|
||||
)
|
||||
|
||||
# Audit log
|
||||
audit_repo = AuditLogRepositoryImpl(db)
|
||||
await audit_repo.create(AuditLog(
|
||||
admin_username=admin.username,
|
||||
action="retry_job",
|
||||
target_type="retry",
|
||||
target_id=id,
|
||||
detail=f"Manual retry triggered for job #{id}",
|
||||
))
|
||||
|
||||
return _retry_to_dict(job)
|
||||
|
||||
|
||||
@retry_router.delete("/{id}", status_code=204)
|
||||
async def delete_retry_job(
|
||||
id: int,
|
||||
admin: Admin = Depends(get_current_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Delete a retry job"""
|
||||
repo = PushRetryJobRepositoryImpl(db)
|
||||
job = await repo.get_by_id(id)
|
||||
if not job:
|
||||
raise HTTPException(status_code=404, detail="Retry job not found")
|
||||
|
||||
# Audit log
|
||||
audit_repo = AuditLogRepositoryImpl(db)
|
||||
await audit_repo.create(AuditLog(
|
||||
admin_username=admin.username,
|
||||
action="delete_retry",
|
||||
target_type="retry",
|
||||
target_id=id,
|
||||
detail=f"Retry job #{id} deleted",
|
||||
))
|
||||
|
||||
await repo.delete(id)
|
||||
|
||||
|
||||
# ── Helper functions ──
|
||||
|
||||
def _schedule_to_dict(schedule: PushSchedule) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user