Files
UHD_Media_Tool/packages/contracts/src/logs.ts
T

118 lines
4.9 KiB
TypeScript

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";