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:
parent
7e40c80410
commit
55e4ce30b8
@ -87,8 +87,8 @@ def _row_to_dict(row) -> dict:
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
async def list_audit_logs(sor, filters: dict = None, limit: int = 100, offset: int = 0):
|
async def list_audit_logs(sor, filters: dict = None, page: int = 1, rows: int = 60):
|
||||||
"""查询审计日志(仅 owner.audit)。"""
|
"""查询审计日志(分页,DataGrid 兼容),返回 {rows, total}。"""
|
||||||
filters = filters or {}
|
filters = filters or {}
|
||||||
where = []
|
where = []
|
||||||
ns = {}
|
ns = {}
|
||||||
@ -109,17 +109,21 @@ async def list_audit_logs(sor, filters: dict = None, limit: int = 100, offset: i
|
|||||||
ns["t"] = filters["to"]
|
ns["t"] = filters["to"]
|
||||||
wsql = (" WHERE " + " AND ".join(where)) if where else ""
|
wsql = (" WHERE " + " AND ".join(where)) if where else ""
|
||||||
try:
|
try:
|
||||||
limit = max(1, min(int(limit or 100), 1000))
|
page = max(1, int(page or 1))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
limit = 100
|
page = 1
|
||||||
try:
|
try:
|
||||||
offset = max(0, int(offset or 0))
|
rows = max(1, min(int(rows or 60), 500))
|
||||||
except (ValueError, TypeError):
|
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(
|
recs = await sor.sqlExe(
|
||||||
"SELECT * FROM sd_audit_logs" + wsql + " ORDER BY created_at DESC LIMIT "
|
"SELECT * FROM sd_audit_logs" + wsql + " ORDER BY created_at DESC LIMIT "
|
||||||
+ str(limit) + " OFFSET " + str(offset), ns)
|
+ str(rows) + " OFFSET " + str(offset), ns)
|
||||||
return [_row_to_dict(r) for r in recs]
|
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,
|
async def backup_audit_logs(sor, user_id: str, username: str, filters: dict = None,
|
||||||
|
|||||||
@ -7,8 +7,10 @@ import os, sys, subprocess
|
|||||||
|
|
||||||
MOD = "app_audit"
|
MOD = "app_audit"
|
||||||
|
|
||||||
# 审计日志 API:仅 owner.audit 角色
|
# 审计日志界面 + API:仅 owner.audit 角色
|
||||||
PATHS_AUDIT = [
|
PATHS_AUDIT = [
|
||||||
|
f"/{MOD}",
|
||||||
|
f"/{MOD}/index.ui",
|
||||||
f"/{MOD}/api/audit.dspy",
|
f"/{MOD}/api/audit.dspy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -41,12 +41,12 @@ if action == 'list':
|
|||||||
'from': (params_kw or {}).get('from', ''),
|
'from': (params_kw or {}).get('from', ''),
|
||||||
'to': (params_kw or {}).get('to', ''),
|
'to': (params_kw or {}).get('to', ''),
|
||||||
}
|
}
|
||||||
limit = (params_kw or {}).get('limit', 100)
|
page = (params_kw or {}).get('page', 1)
|
||||||
offset = (params_kw or {}).get('offset', 0)
|
rows = (params_kw or {}).get('rows', 60)
|
||||||
try:
|
try:
|
||||||
async with get_sor_context(request._run_ns, 'pipeline') as sor:
|
async with get_sor_context(request._run_ns, 'pipeline') as sor:
|
||||||
rows = await list_audit_logs(sor, filters, limit, offset)
|
r = await list_audit_logs(sor, filters, page, rows)
|
||||||
return json.dumps({'ok': True, 'logs': rows}, ensure_ascii=False)
|
return json.dumps(r, ensure_ascii=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)
|
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)
|
||||||
|
|
||||||
|
|||||||
34
wwwroot/index.ui
Normal file
34
wwwroot/index.ui
Normal 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}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user