feat(system): retention cleanup jobs, storage panel API and health storage level (CACHE-2)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 17:16:50 +08:00
1 parent d13c18b4b1
commit 05b163ce32
9 files changed
+1254 -9

No files matched your search

+101
View File
@@ -481,9 +481,110 @@ export const SystemHealth = z.object({
),
failedRuns24h: NonNegInt,
}),
/** 存储预算(CACHE-2):PG 库体积、图片缓存、备份、会话与证据对照 settings.storage.budgets;明细见 GET /system/storage */
storage: z.object({
level: HealthLevel,
/** 超出告警线 / 预算的项(人读文案) */
alerts: z.array(z.string()),
}),
});
export type SystemHealth = z.infer<typeof SystemHealth>;
// ── 存储预算与保留(CACHE-2;GET /system/storage、POST /system/storage/cleanup/{target},仅 admin) ──
/** 手动触发的清理目标;每个对应一个维护组定时任务 */
export const StorageCleanupTarget = z.enum([
"observation",
"sse_event",
"heartbeat",
"task_run",
"agent_event",
"audit",
"capability_stats",
"identity_pool",
"analyze",
"images",
"kv_cache",
]);
export type StorageCleanupTarget = z.infer<typeof StorageCleanupTarget>;
export const StorageCleanupParam = z.object({ target: StorageCleanupTarget });
export const StorageCleanupResult = z.object({
target: StorageCleanupTarget,
/** 对应的定时任务 key */
jobId: z.string(),
/** false = 已在排队或运行中(请求被合并) */
queued: z.boolean(),
});
export type StorageCleanupResult = z.infer<typeof StorageCleanupResult>;
/** 一项占用对照预算 */
export const StorageBudgetUsage = z.object({
usedBytes: NonNegInt.nullable(),
/** 预算(critical 线);无预算为 null */
budgetBytes: NonNegInt.nullable(),
/** 告警线;无单独告警线时为预算的 80% */
warnBytes: NonNegInt.nullable(),
level: HealthLevel,
error: z.string().nullable(),
});
export type StorageBudgetUsage = z.infer<typeof StorageBudgetUsage>;
export const SystemStorage = z.object({
/** 各项最差档(unknown 不参与) */
level: HealthLevel,
checkedAt: Timestamp,
/** 目录用量的统计时间(目录遍历结果缓存 5 分钟) */
dirsScannedAt: Timestamp.nullable(),
db: StorageBudgetUsage.extend({
/** 体积前 15 的表(含索引与 TOAST) */
tables: z.array(
z.object({
schema: z.string(),
name: z.string(),
totalBytes: NonNegInt,
tableBytes: NonNegInt,
indexBytes: NonNegInt,
/** 估算行数(pg_class.reltuples);未 ANALYZE 为 null */
rowsEstimate: NonNegInt.nullable(),
deadRows: NonNegInt.nullable(),
lastAutovacuumAt: Timestamp.nullable(),
lastAnalyzeAt: Timestamp.nullable(),
}),
),
}),
/** PG kv_cache 各 region(条数含未清理的过期行) */
kvCache: z.array(z.object({ region: z.string(), entries: NonNegInt, expired: NonNegInt, bytes: NonNegInt })),
images: StorageBudgetUsage.extend({ files: NonNegInt.nullable() }),
backups: StorageBudgetUsage.extend({ count: NonNegInt.nullable() }),
sessionsEvidence: StorageBudgetUsage,
/** DATA_DIR 下各一级目录(cache 再细分一层)的用量 */
dataDir: z.array(
z.object({
name: z.string(),
path: z.string(),
usedBytes: NonNegInt,
files: NonNegInt,
error: z.string().nullable(),
}),
),
/** 各清理任务的当前设置与最近运行 */
cleanup: z.array(
z.object({
target: StorageCleanupTarget,
jobId: z.string(),
/** 保留期说明(如 "90 天"、"24 小时"、"容量 20 GB") */
retention: z.string().nullable(),
/** 定时任务不存在(未登记 / 调度器未同步)时为 null */
enabled: z.boolean().nullable(),
lastRunAt: Timestamp.nullable(),
lastStatus: z.string().nullable(),
lastSummary: z.string().nullable(),
nextRunAt: Timestamp.nullable(),
}),
),
});
export type SystemStorage = z.infer<typeof SystemStorage>;
export const SystemHealthHistoryQuery = z.object({
/** 回看小时数,1–168,缺省 6 */
hours: z.coerce.number().int().min(1).max(168).default(6),