feat: app_audit 审计日志全局模块(独立仓库)
- audit_service.py: audit_log 写入 + is_audit_role + list/backup/delete - init.py: load_app_audit 建 sd_audit_logs 表 + owner.audit 角色 - audit.dspy: list/backup/delete 仅 owner.audit(审计独立性) - models + load_path.py + README
This commit is contained in:
commit
6bcc1469eb
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
50
README.md
Normal file
50
README.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# app_audit - 审计日志全局模块
|
||||||
|
|
||||||
|
独立设计的全局审计模块。审计日志 append-only,查看/备份/删除权限仅赋予
|
||||||
|
`owner.audit` 角色(审计独立性:`owner.superuser` 也无权访问,防管理员删自己的记录)。
|
||||||
|
|
||||||
|
## 结构
|
||||||
|
|
||||||
|
```
|
||||||
|
app_audit/
|
||||||
|
├── app_audit/
|
||||||
|
│ ├── __init__.py # 导出 audit_log / is_audit_role 等
|
||||||
|
│ ├── audit_service.py # 核心逻辑
|
||||||
|
│ └── init.py # load_app_audit(建表 + owner.audit 角色)
|
||||||
|
├── models/
|
||||||
|
│ └── sd_audit_logs.json
|
||||||
|
├── wwwroot/
|
||||||
|
│ └── api/audit.dspy # list/backup/delete(仅 owner.audit)
|
||||||
|
└── scripts/load_path.py # RBAC 权限注册
|
||||||
|
```
|
||||||
|
|
||||||
|
## 审计事件清单(action 取值)
|
||||||
|
|
||||||
|
- 认证:`login` / `login_fail` / `logout`
|
||||||
|
- 权限:`role_change` / `perm_change` / `user_role_change`
|
||||||
|
- 工作环境:`work_env_set` / `org_key_gen` / `remote_bwrap`
|
||||||
|
- 部署账号:`account_create` / `account_remove` / `sandbox_run`
|
||||||
|
- 用户机构:`user_create` / `user_disable` / `user_delete` / `org_change`
|
||||||
|
- 审计自身:`audit_delete` / `audit_backup`
|
||||||
|
|
||||||
|
## 接入
|
||||||
|
|
||||||
|
宿主应用在 `init()` 里调用:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app_audit.init import load_app_audit
|
||||||
|
load_app_audit()
|
||||||
|
```
|
||||||
|
|
||||||
|
其他模块在关键操作时写入审计:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app_audit import audit_log
|
||||||
|
await audit_log(sor, user_id, username, "work_env_set", target=..., detail=..., result="ok")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 权限隔离
|
||||||
|
|
||||||
|
- `owner.audit` 角色:查看/备份/删除审计日志的唯一角色
|
||||||
|
- `owner.superuser` 也无权访问(审计独立性)
|
||||||
|
- `audit_delete` 留痕记录永不删除(防自删)
|
||||||
31
app_audit/__init__.py
Normal file
31
app_audit/__init__.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"""
|
||||||
|
app_audit - 审计日志全局模块(独立仓库)。
|
||||||
|
|
||||||
|
审计日志 append-only,查看/备份/删除权限仅赋予 owner.audit 角色。
|
||||||
|
其他模块通过 `from app_audit import audit_log` 在关键操作时写入审计。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .audit_service import (
|
||||||
|
audit_log,
|
||||||
|
is_audit_role,
|
||||||
|
list_audit_logs,
|
||||||
|
backup_audit_logs,
|
||||||
|
delete_audit_logs,
|
||||||
|
VALID_ACTIONS,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .init import load_app_audit, MODULE_NAME, MODULE_VERSION
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"audit_log",
|
||||||
|
"is_audit_role",
|
||||||
|
"list_audit_logs",
|
||||||
|
"backup_audit_logs",
|
||||||
|
"delete_audit_logs",
|
||||||
|
"VALID_ACTIONS",
|
||||||
|
"load_app_audit",
|
||||||
|
"MODULE_NAME",
|
||||||
|
"MODULE_VERSION",
|
||||||
|
]
|
||||||
160
app_audit/audit_service.py
Normal file
160
app_audit/audit_service.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
"""
|
||||||
|
app_audit/audit_service.py - 审计日志全局模块
|
||||||
|
|
||||||
|
独立设计的全局审计模块(独立仓库 app_audit)。审计日志 append-only,
|
||||||
|
查看/备份/删除权限仅赋予 `owner.audit` 角色(审计独立性:owner.superuser 也不能看,
|
||||||
|
防管理员删自己的记录)。
|
||||||
|
|
||||||
|
- audit_log: 各模块在关键操作时写入审计(系统自动,非用户触发)
|
||||||
|
- is_audit_role: 判断用户是否 owner.audit(API 层权限校验用)
|
||||||
|
- list_audit_logs / backup_audit_logs / delete_audit_logs: 仅 owner.audit
|
||||||
|
|
||||||
|
审计事件清单(action 取值):
|
||||||
|
认证:login / login_fail / logout
|
||||||
|
权限:role_change / perm_change / user_role_change
|
||||||
|
工作环境:work_env_set / org_key_gen / remote_bwrap
|
||||||
|
部署账号:account_create / account_remove / sandbox_run
|
||||||
|
用户机构:user_create / user_disable / user_delete / org_change
|
||||||
|
审计自身:audit_delete / audit_backup
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("app_audit")
|
||||||
|
|
||||||
|
# 合法审计动作白名单
|
||||||
|
VALID_ACTIONS = {
|
||||||
|
"login", "login_fail", "logout",
|
||||||
|
"role_change", "perm_change", "user_role_change",
|
||||||
|
"work_env_set", "org_key_gen", "remote_bwrap",
|
||||||
|
"account_create", "account_remove", "sandbox_run",
|
||||||
|
"user_create", "user_disable", "user_delete", "org_change",
|
||||||
|
"audit_delete", "audit_backup",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def audit_log(sor, user_id: str, username: str, action: str,
|
||||||
|
target: str = "", detail: str = "", result: str = "ok",
|
||||||
|
client_ip: str = "") -> bool:
|
||||||
|
"""写入一条审计日志(append-only,不 update)。
|
||||||
|
|
||||||
|
审计写入失败不应阻断主流程,返回 False 由调用方决定是否处理。
|
||||||
|
"""
|
||||||
|
if action not in VALID_ACTIONS:
|
||||||
|
action = "unknown"
|
||||||
|
try:
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
data = {
|
||||||
|
"id": getID(),
|
||||||
|
"user_id": user_id or "",
|
||||||
|
"username": username or "",
|
||||||
|
"action": action,
|
||||||
|
"target": (target or "")[:200],
|
||||||
|
"detail": (detail or "")[:2000],
|
||||||
|
"result": result or "ok",
|
||||||
|
"client_ip": (client_ip or "")[:64],
|
||||||
|
}
|
||||||
|
await sor.C("sd_audit_logs", data)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("audit_log failed: %s", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def is_audit_role(sor, user_id: str) -> bool:
|
||||||
|
"""判断用户是否 owner.audit 角色。"""
|
||||||
|
if not user_id:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
recs = await sor.sqlExe(
|
||||||
|
"SELECT 1 FROM userrole WHERE userid=${u}$ AND roleid='owner.audit' LIMIT 1",
|
||||||
|
{"u": user_id})
|
||||||
|
return bool(recs)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("is_audit_role failed: %s", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _row_to_dict(row) -> dict:
|
||||||
|
"""DB 行 → dict(created_at 等 datetime 转字符串)。"""
|
||||||
|
d = {}
|
||||||
|
for k in ("id", "user_id", "username", "action", "target", "detail",
|
||||||
|
"result", "client_ip", "created_at"):
|
||||||
|
v = getattr(row, k, None)
|
||||||
|
if hasattr(v, "isoformat"):
|
||||||
|
v = v.isoformat()
|
||||||
|
d[k] = v
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
async def list_audit_logs(sor, filters: dict = None, limit: int = 100, offset: int = 0):
|
||||||
|
"""查询审计日志(仅 owner.audit)。"""
|
||||||
|
filters = filters or {}
|
||||||
|
where = []
|
||||||
|
ns = {}
|
||||||
|
if filters.get("user_id"):
|
||||||
|
where.append("user_id=${u}$")
|
||||||
|
ns["u"] = filters["user_id"]
|
||||||
|
if filters.get("username"):
|
||||||
|
where.append("username=${un}$")
|
||||||
|
ns["un"] = filters["username"]
|
||||||
|
if filters.get("action"):
|
||||||
|
where.append("action=${a}$")
|
||||||
|
ns["a"] = filters["action"]
|
||||||
|
if filters.get("from"):
|
||||||
|
where.append("created_at>=${f}$")
|
||||||
|
ns["f"] = filters["from"]
|
||||||
|
if filters.get("to"):
|
||||||
|
where.append("created_at<=${t}$")
|
||||||
|
ns["t"] = filters["to"]
|
||||||
|
wsql = (" WHERE " + " AND ".join(where)) if where else ""
|
||||||
|
try:
|
||||||
|
limit = max(1, min(int(limit or 100), 1000))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
limit = 100
|
||||||
|
try:
|
||||||
|
offset = max(0, int(offset or 0))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
offset = 0
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
|
async def backup_audit_logs(sor, user_id: str, username: str, filters: dict = None,
|
||||||
|
client_ip: str = "") -> dict:
|
||||||
|
"""导出审计日志(仅 owner.audit),返回 JSON 内容供下载。"""
|
||||||
|
# 查最近 10000 条(备份上限,避免一次拉全表拖垮内存)
|
||||||
|
recs = await sor.sqlExe(
|
||||||
|
"SELECT * FROM sd_audit_logs ORDER BY created_at DESC LIMIT 10000", {})
|
||||||
|
rows = [_row_to_dict(r) for r in recs]
|
||||||
|
# 备份操作本身也审计
|
||||||
|
await audit_log(sor, user_id, username, "audit_backup",
|
||||||
|
target="sd_audit_logs", detail="备份 " + str(len(rows)) + " 条",
|
||||||
|
result="ok", client_ip=client_ip)
|
||||||
|
return {"ok": True, "count": len(rows), "data": rows}
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_audit_logs(sor, user_id: str, username: str, before: str = "",
|
||||||
|
client_ip: str = "") -> dict:
|
||||||
|
"""删除审计日志(仅 owner.audit)。before 为时间戳,删除早于该时间的日志。
|
||||||
|
|
||||||
|
删除操作本身也会审计(audit_delete 留痕),且 audit_delete 记录**永不删除**,
|
||||||
|
保证删除操作的证据永久保留(防自删)。
|
||||||
|
"""
|
||||||
|
ns = {}
|
||||||
|
# 排除 audit_delete 记录,保证删除留痕不被删
|
||||||
|
if before:
|
||||||
|
wsql = " WHERE created_at<${b}$ AND action != 'audit_delete'"
|
||||||
|
ns["b"] = before
|
||||||
|
else:
|
||||||
|
wsql = " WHERE action != 'audit_delete'"
|
||||||
|
# 先记录删除操作(在删除前写,确保删除动作本身留痕)
|
||||||
|
await audit_log(sor, user_id, username, "audit_delete",
|
||||||
|
target="sd_audit_logs", detail="删除 before=" + (before or "全部"),
|
||||||
|
result="ok", client_ip=client_ip)
|
||||||
|
recs = await sor.sqlExe("SELECT COUNT(*) AS c FROM sd_audit_logs" + wsql, ns)
|
||||||
|
count = getattr(recs[0], "c", 0) if recs else 0
|
||||||
|
await sor.sqlExe("DELETE FROM sd_audit_logs" + wsql, ns)
|
||||||
|
return {"ok": True, "deleted": count}
|
||||||
42
app_audit/init.py
Normal file
42
app_audit/init.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"""
|
||||||
|
app_audit/init.py - 审计模块加载入口。
|
||||||
|
|
||||||
|
独立审计模块:建 sd_audit_logs 表 + owner.audit 角色(审计独立性,
|
||||||
|
owner.superuser 也无权访问审计日志)。宿主应用调用 load_app_audit() 即可。
|
||||||
|
"""
|
||||||
|
|
||||||
|
MODULE_NAME = "app_audit"
|
||||||
|
MODULE_VERSION = "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
def load_app_audit():
|
||||||
|
"""注册审计模块的建表 + owner.audit 角色到启动流程。"""
|
||||||
|
from ahserver.configuredServer import add_startup
|
||||||
|
|
||||||
|
async def _init_audit(app):
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from appPublic.log import debug
|
||||||
|
try:
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext("pipeline") as sor:
|
||||||
|
# 审计日志表(append-only,全局)
|
||||||
|
await sor.sqlExe(
|
||||||
|
"CREATE TABLE IF NOT EXISTS sd_audit_logs ("
|
||||||
|
"id varchar(32) NOT NULL, user_id varchar(32), username varchar(100),"
|
||||||
|
"action varchar(50) NOT NULL, target varchar(200), detail text,"
|
||||||
|
"result varchar(10), client_ip varchar(64),"
|
||||||
|
"created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,"
|
||||||
|
"PRIMARY KEY (id), KEY idx_user (user_id), KEY idx_action (action),"
|
||||||
|
"KEY idx_created (created_at)"
|
||||||
|
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", {})
|
||||||
|
debug("app_audit: sd_audit_logs table ready")
|
||||||
|
# owner.audit 角色(审计独立角色)
|
||||||
|
await sor.sqlExe(
|
||||||
|
"INSERT IGNORE INTO role (id, orgtypeid, name) "
|
||||||
|
"VALUES ('owner.audit', 'owner', 'audit')", {})
|
||||||
|
debug("app_audit: owner.audit role ready")
|
||||||
|
except Exception as e:
|
||||||
|
debug("app_audit init: " + str(e))
|
||||||
|
|
||||||
|
add_startup(_init_audit)
|
||||||
|
return True
|
||||||
75
models/sd_audit_logs.json
Normal file
75
models/sd_audit_logs.json
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"summary": [
|
||||||
|
{
|
||||||
|
"name": "sd_audit_logs",
|
||||||
|
"title": "审计日志表",
|
||||||
|
"primary": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"catelog": "entity"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "主键ID",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"title": "操作者ID",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": "yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"title": "操作者用户名",
|
||||||
|
"type": "str",
|
||||||
|
"length": 100,
|
||||||
|
"nullable": "yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "action",
|
||||||
|
"title": "审计动作",
|
||||||
|
"type": "str",
|
||||||
|
"length": 50,
|
||||||
|
"nullable": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "target",
|
||||||
|
"title": "操作对象",
|
||||||
|
"type": "str",
|
||||||
|
"length": 200,
|
||||||
|
"nullable": "yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "detail",
|
||||||
|
"title": "详情",
|
||||||
|
"type": "text",
|
||||||
|
"nullable": "yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "result",
|
||||||
|
"title": "结果",
|
||||||
|
"type": "str",
|
||||||
|
"length": 10,
|
||||||
|
"nullable": "yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "client_ip",
|
||||||
|
"title": "来源IP",
|
||||||
|
"type": "str",
|
||||||
|
"length": 64,
|
||||||
|
"nullable": "yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"title": "创建时间",
|
||||||
|
"type": "datetime",
|
||||||
|
"nullable": "yes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
pyproject.toml
Normal file
13
pyproject.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=45", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "app_audit"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "Audit log global module — append-only audit trail with owner.audit role isolation"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["app_audit*"]
|
||||||
49
scripts/load_path.py
Normal file
49
scripts/load_path.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""RBAC path registration for app_audit module.
|
||||||
|
|
||||||
|
审计 API 仅 owner.audit 角色(审计独立性:superuser 也不给)。
|
||||||
|
"""
|
||||||
|
import os, sys, subprocess
|
||||||
|
|
||||||
|
MOD = "app_audit"
|
||||||
|
|
||||||
|
# 审计日志 API:仅 owner.audit 角色
|
||||||
|
PATHS_AUDIT = [
|
||||||
|
f"/{MOD}/api/audit.dspy",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def find_app_root():
|
||||||
|
# 从 scripts/ 向上逐层查找 set_role_perm.py(位于宿主 app 根目录)
|
||||||
|
d = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
for _ in range(6):
|
||||||
|
if os.path.isfile(os.path.join(d, "set_role_perm.py")):
|
||||||
|
return d
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
break
|
||||||
|
d = parent
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
root = find_app_root()
|
||||||
|
if not root:
|
||||||
|
print("ERROR: app root not found")
|
||||||
|
sys.exit(1)
|
||||||
|
set_perm = os.path.join(root, "set_role_perm.py")
|
||||||
|
py = sys.executable
|
||||||
|
count = 0
|
||||||
|
for role, paths in [("owner.audit", PATHS_AUDIT)]:
|
||||||
|
for path in paths:
|
||||||
|
cmd = [py, set_perm, role, path]
|
||||||
|
r = subprocess.run(cmd, capture_output=True, text=True, cwd=root)
|
||||||
|
if r.returncode == 0:
|
||||||
|
count += 1
|
||||||
|
else:
|
||||||
|
print(f" WARN: {path} -> {r.stderr.strip()[-80:]}")
|
||||||
|
print(f"Registered {count}/{len(PATHS_AUDIT)} paths for {MOD}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
71
wwwroot/api/audit.dspy
Normal file
71
wwwroot/api/audit.dspy
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# app_audit audit.dspy - 审计日志 API
|
||||||
|
# 查看/备份/删除 仅 owner.audit 角色(审计独立性:superuser 也无权访问)
|
||||||
|
# action: list / backup / delete
|
||||||
|
|
||||||
|
user_id = await get_user()
|
||||||
|
if not user_id:
|
||||||
|
return json.dumps({'ok': False, 'error': '未登录'}, ensure_ascii=False)
|
||||||
|
|
||||||
|
action = (params_kw or {}).get('action', 'list')
|
||||||
|
|
||||||
|
from app_audit.audit_service import (
|
||||||
|
is_audit_role, list_audit_logs, backup_audit_logs, delete_audit_logs,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 查 username 和 client_ip
|
||||||
|
username = user_id
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'pipeline') as _sor:
|
||||||
|
_u = await _sor.sqlExe("SELECT username FROM users WHERE id=${u}$", {'u': user_id})
|
||||||
|
if _u:
|
||||||
|
username = getattr(_u[0], 'username', user_id) or user_id
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
client_ip = ''
|
||||||
|
try:
|
||||||
|
client_ip = request.get('client_ip', '') or ''
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 权限校验:所有 action 仅 owner.audit(superuser 也不行)
|
||||||
|
async with get_sor_context(request._run_ns, 'pipeline') as _sor:
|
||||||
|
if not await is_audit_role(_sor, user_id):
|
||||||
|
return json.dumps({'ok': False, 'error': '仅 owner.audit 角色可访问审计日志'}, ensure_ascii=False)
|
||||||
|
|
||||||
|
if action == 'list':
|
||||||
|
filters = {
|
||||||
|
'user_id': (params_kw or {}).get('user_id', ''),
|
||||||
|
'username': (params_kw or {}).get('username', ''),
|
||||||
|
'action': (params_kw or {}).get('act', ''),
|
||||||
|
'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)
|
||||||
|
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)
|
||||||
|
except Exception as e:
|
||||||
|
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)
|
||||||
|
|
||||||
|
elif action == 'backup':
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'pipeline') as sor:
|
||||||
|
r = await backup_audit_logs(sor, user_id, username, None, client_ip)
|
||||||
|
return json.dumps(r, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)
|
||||||
|
|
||||||
|
elif action == 'delete':
|
||||||
|
before = (params_kw or {}).get('before', '')
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'pipeline') as sor:
|
||||||
|
r = await delete_audit_logs(sor, user_id, username, before, client_ip)
|
||||||
|
return json.dumps(r, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
return json.dumps({'ok': False, 'error': str(e)}, ensure_ascii=False)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return json.dumps({'ok': False, 'error': '未知 action: ' + str(action)}, ensure_ascii=False)
|
||||||
Loading…
x
Reference in New Issue
Block a user