feat(contracts): 日志列表 / 日志流与资源监控契约,审计新增登录失败
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
ad3bf8926b
commit
01c68f9771
9 files changed
+245
No files matched your search
@@ -0,0 +1,117 @@
|
||||
import { z } from "zod";
|
||||
import { cursorPage, Timestamp } from "./common.ts";
|
||||
|
||||
// 日志模块(界面第二轮 V 线):业务任务、定时 / 自动任务运行、登录、审计合成一份按时间倒序的列表。
|
||||
// 不新建表:GET /logs 在查询时把各来源表 union 起来(engine workflow_runs / scheduled_job_run /
|
||||
// agent_run(trigger=event)/ audit_event),游标按 (时间, id) 分页;GET /logs/stream 用 SSE 追加新日志。
|
||||
|
||||
/**
|
||||
* 类型:
|
||||
* - task 业务任务(工作流引擎的一次运行)
|
||||
* - auto 定时与自动:定时任务运行记录(含智能体巡检)、智能体后台调查
|
||||
* - auth 登录:管理员登录 / 退出 / 登录失败 / 二次验证 / Passkey / 改密、API Key 的增改撤
|
||||
* - audit 其余审计:设置、凭证、实例、文件操作、智能体操作等
|
||||
*/
|
||||
export const LogType = z.enum(["task", "auto", "auth", "audit"]);
|
||||
export type LogType = z.infer<typeof LogType>;
|
||||
|
||||
/** 级别 / 结果:进行中、成功、信息(普通操作)、警告(取消、跳过以外的非成功,如登录失败、被拒绝、超出预算)、失败 */
|
||||
export const LogLevel = z.enum(["running", "success", "info", "warn", "error"]);
|
||||
export type LogLevel = z.infer<typeof LogLevel>;
|
||||
|
||||
/** 来源表 */
|
||||
export const LogSource = z.enum(["engine_run", "schedule_run", "agent_run", "audit_event"]);
|
||||
export type LogSource = z.infer<typeof LogSource>;
|
||||
|
||||
export const LogObject = z.object({
|
||||
/** 对象类型:审计的 target_type;任务按业务对象换算(download_task / organization_record / agent_run / scheduled_job …) */
|
||||
type: z.string(),
|
||||
id: z.string().nullable(),
|
||||
/** 展示名(作品、下载任务、定时任务名等);取不到为 null */
|
||||
label: z.string().nullable(),
|
||||
});
|
||||
export type LogObject = z.infer<typeof LogObject>;
|
||||
|
||||
export const LogEntry = z.object({
|
||||
/** <source>:<原始 id>,全局唯一,也是游标的一部分 */
|
||||
id: z.string(),
|
||||
type: LogType,
|
||||
source: LogSource,
|
||||
/** 原始记录 id(引擎 run id、scheduled_job_run id、agent_run id、audit_event id) */
|
||||
refId: z.string(),
|
||||
/** 发生时间:任务为创建时间、定时任务为开始时间、审计为事件时间 */
|
||||
at: Timestamp,
|
||||
finishedAt: Timestamp.nullable(),
|
||||
durationMs: z.number().nonnegative().nullable(),
|
||||
level: LogLevel,
|
||||
/** 来源表的原始状态(completed / failed / ok / skipped / admin.login_success …) */
|
||||
status: z.string().nullable(),
|
||||
/** 一行摘要:任务业务标题或工作流名、定时任务名、智能体目标、审计动作 */
|
||||
title: z.string(),
|
||||
/** 第二行:错误、运行摘要、登录 IP 等 */
|
||||
message: z.string().nullable(),
|
||||
object: LogObject.nullable(),
|
||||
/** 操作者(审计)或触发来源(任务 / 定时任务) */
|
||||
actor: z.object({ type: z.string(), id: z.string().nullable() }).nullable(),
|
||||
/** 各来源特有字段(workflowName、business、trigger、action、payload …),详情页用 */
|
||||
extra: z.record(z.string(), z.unknown()),
|
||||
});
|
||||
export type LogEntry = z.infer<typeof LogEntry>;
|
||||
|
||||
/** 单值或逗号分隔多值(type=task,auto) */
|
||||
function csv<T extends string>(e: z.ZodType<T>, name: string) {
|
||||
return z
|
||||
.string()
|
||||
.max(200)
|
||||
.transform((s, ctx) => {
|
||||
const out: T[] = [];
|
||||
for (const p of s
|
||||
.split(",")
|
||||
.map((x) => x.trim())
|
||||
.filter(Boolean)) {
|
||||
const r = e.safeParse(p);
|
||||
if (!r.success) {
|
||||
ctx.addIssue({ code: "custom", message: `未知 ${name}: ${p}` });
|
||||
return z.NEVER;
|
||||
}
|
||||
out.push(r.data);
|
||||
}
|
||||
return out;
|
||||
})
|
||||
.optional();
|
||||
}
|
||||
|
||||
/** 列表与流共用的筛选 */
|
||||
export const LogFilter = z.object({
|
||||
/** 逗号分隔多值;缺省全部 */
|
||||
type: csv(LogType, "type"),
|
||||
level: csv(LogLevel, "level"),
|
||||
from: Timestamp.optional(),
|
||||
to: Timestamp.optional(),
|
||||
/** 关键词:标题、摘要、错误、动作、对象 id(不区分大小写) */
|
||||
q: z.string().trim().max(200).optional(),
|
||||
/** 对象:与 LogEntry.object 的 type / id 匹配;只给 objectId 时不限类型 */
|
||||
objectType: z.string().max(100).optional(),
|
||||
objectId: z.string().max(200).optional(),
|
||||
/** "1" = 也列系统任务(没有业务对象的引擎运行、定时任务触发的成功运行);缺省隐藏,按对象筛选时自动包含 */
|
||||
system: z.enum(["0", "1"]).optional(),
|
||||
});
|
||||
export type LogFilter = z.infer<typeof LogFilter>;
|
||||
|
||||
export const LogQuery = LogFilter.extend({
|
||||
cursor: z.string().optional(),
|
||||
limit: z.coerce.number().int().min(1).max(200).default(50),
|
||||
});
|
||||
export type LogQuery = z.infer<typeof LogQuery>;
|
||||
|
||||
export const LogPage = cursorPage(LogEntry);
|
||||
export type LogPage = z.infer<typeof LogPage>;
|
||||
|
||||
export const LogStreamQuery = LogFilter.extend({
|
||||
/** 只推这个时间之后的新日志(客户端列表里最新一条的时间);缺省为连接时刻 */
|
||||
since: Timestamp.optional(),
|
||||
});
|
||||
export type LogStreamQuery = z.infer<typeof LogStreamQuery>;
|
||||
|
||||
/** SSE 事件名:entry = 新日志或已推过的日志状态有变化(按 id 覆盖) */
|
||||
export const LOG_STREAM_EVENT = "log.entry";
|
||||
Reference in new issue
Block a user