diff --git a/app_audit/audit_service.py b/app_audit/audit_service.py index d8b7292..0edc60e 100644 --- a/app_audit/audit_service.py +++ b/app_audit/audit_service.py @@ -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, diff --git a/scripts/load_path.py b/scripts/load_path.py index bdec1f9..6fbb440 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -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", ] diff --git a/wwwroot/api/audit.dspy b/wwwroot/api/audit.dspy index c6af60f..173c3c6 100644 --- a/wwwroot/api/audit.dspy +++ b/wwwroot/api/audit.dspy @@ -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) diff --git a/wwwroot/index.ui b/wwwroot/index.ui new file mode 100644 index 0000000..8b72f70 --- /dev/null +++ b/wwwroot/index.ui @@ -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} + ] + } + } + ] +}