feat: 审计日志界面 + DataGrid 分页查询

- index.ui: DataGrid 审计日志列表界面
- audit_service: list_audit_logs 改分页返回 {rows,total}
- audit.dspy: list 返回 DataGrid 格式
- load_path: 注册 /app_audit 界面权限
This commit is contained in:
yumoqing 2026-08-14 13:23:40 +08:00
parent 7e40c80410
commit 55e4ce30b8
4 changed files with 53 additions and 13 deletions

View File

@ -87,8 +87,8 @@ def _row_to_dict(row) -> dict:
return d
async def list_audit_logs(sor, filters: dict = None, limit: int = 100, offset: int = 0):
"""查询审计日志(仅 owner.audit"""
async def list_audit_logs(sor, filters: dict = None, page: int = 1, rows: int = 60):
"""查询审计日志(分页DataGrid 兼容),返回 {rows, total}"""
filters = filters or {}
where = []
ns = {}
@ -109,17 +109,21 @@ async def list_audit_logs(sor, filters: dict = None, limit: int = 100, offset: i
ns["t"] = filters["to"]
wsql = (" WHERE " + " AND ".join(where)) if where else ""
try:
limit = max(1, min(int(limit or 100), 1000))
page = max(1, int(page or 1))
except (ValueError, TypeError):
limit = 100
page = 1
try:
offset = max(0, int(offset or 0))
rows = max(1, min(int(rows or 60), 500))
except (ValueError, TypeError):
offset = 0
rows = 60
total_recs = await sor.sqlExe(
"SELECT COUNT(*) AS c FROM sd_audit_logs" + wsql, ns)
total = getattr(total_recs[0], "c", 0) if total_recs else 0
offset = (page - 1) * rows
recs = await sor.sqlExe(
"SELECT * FROM sd_audit_logs" + wsql + " ORDER BY created_at DESC LIMIT "
+ str(limit) + " OFFSET " + str(offset), ns)
return [_row_to_dict(r) for r in recs]
+ str(rows) + " OFFSET " + str(offset), ns)
return {"rows": [_row_to_dict(r) for r in recs], "total": total}
async def backup_audit_logs(sor, user_id: str, username: str, filters: dict = None,

View File

@ -7,8 +7,10 @@ import os, sys, subprocess
MOD = "app_audit"
# 审计日志 API仅 owner.audit 角色
# 审计日志界面 + API仅 owner.audit 角色
PATHS_AUDIT = [
f"/{MOD}",
f"/{MOD}/index.ui",
f"/{MOD}/api/audit.dspy",
]

View File

@ -41,12 +41,12 @@ if action == 'list':
'from': (params_kw or {}).get('from', ''),
'to': (params_kw or {}).get('to', ''),
}
limit = (params_kw or {}).get('limit', 100)
offset = (params_kw or {}).get('offset', 0)
page = (params_kw or {}).get('page', 1)
rows = (params_kw or {}).get('rows', 60)
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
rows = await list_audit_logs(sor, filters, limit, offset)
return json.dumps({'ok': True, 'logs': rows}, ensure_ascii=False)
r = await list_audit_logs(sor, filters, page, rows)
return json.dumps(r, ensure_ascii=False)
except Exception as e:
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)

34
wwwroot/index.ui Normal file
View File

@ -0,0 +1,34 @@
{
"widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "padding": "24px", "gap": "16px"},
"subwidgets": [
{
"widgettype": "Title2",
"options": {"text": "审计日志"}
},
{
"widgettype": "Text",
"options": {"text": "记录关键操作,仅 owner.audit 角色可查看", "cfontsize": 1.1, "color": "#888"}
},
{
"widgettype": "DataGrid",
"options": {
"width": "100%",
"height": "100%",
"dataurl": "{{entire_url('/app_audit/api/audit.dspy')}}",
"method": "POST",
"params": {"action": "list"},
"row_height": 40,
"fields": [
{"name": "created_at", "label": "时间", "datatype": "str", "width": 180},
{"name": "username", "label": "用户", "datatype": "str", "width": 120},
{"name": "action", "label": "动作", "datatype": "str", "width": 140},
{"name": "target", "label": "对象", "datatype": "str", "width": 180},
{"name": "result", "label": "结果", "datatype": "str", "width": 80},
{"name": "client_ip", "label": "IP", "datatype": "str", "width": 130},
{"name": "detail", "label": "详情", "datatype": "str", "width": 320}
]
}
}
]
}