import { z } from "zod"; import * as auth from "../auth.ts"; import { cursorPage, HealthStatus, IdParam } from "../common.ts"; import * as events from "../events.ts"; import * as ops from "../ops.ts"; import { EngineRunIdParam, ep, OkResult } from "./shared.ts"; export const systemApi = { // ── 登录门禁 ── /** /auth/setup、/auth/login、/auth/session 免鉴权;改密与 TOTP 只接受控制台 cookie 会话 */ auth: { setup: ep({ method: "post", path: "/auth/setup", scope: "read", auth: "public", summary: "首次初始化管理员", body: auth.AdminSetupRequest, response: auth.SessionInfo, }), login: ep({ method: "post", path: "/auth/login", scope: "read", auth: "public", summary: "管理员登录", body: auth.LoginRequest, response: auth.LoginResult, }), logout: ep({ method: "post", path: "/auth/logout", scope: "read", summary: "注销当前会话", response: OkResult }), session: ep({ method: "get", path: "/auth/session", scope: "read", auth: "public", summary: "当前登录态(免鉴权)", response: auth.SessionInfo, }), password: ep({ method: "post", path: "/auth/password", scope: "admin", auth: "session", summary: "修改密码(吊销其它会话)", body: auth.PasswordChangeRequest, response: auth.PasswordChangeResult, }), totpStatus: ep({ method: "get", path: "/auth/totp", scope: "admin", auth: "session", summary: "TOTP 状态", response: auth.TotpStatus, }), totp: ep({ method: "post", path: "/auth/totp", scope: "admin", auth: "session", summary: "TOTP 绑定/启用/停用/重新生成恢复码", body: auth.TotpAction, response: auth.TotpActionResult, }), totpSetup: ep({ method: "get", path: "/auth/totp/setup", scope: "admin", auth: "session", summary: "待确认的 TOTP 密钥与 otpauth URL(前端渲染二维码)", response: auth.TotpSetup, }), passkeys: ep({ method: "get", path: "/auth/passkeys", scope: "admin", auth: "session", summary: "已登记的 Passkey", response: auth.PasskeyList, }), passkeyRegisterOptions: ep({ method: "post", path: "/auth/passkeys/register/options", scope: "admin", auth: "session", summary: "Passkey 注册挑战", response: auth.PasskeyChallenge, }), passkeyRegister: ep({ method: "post", path: "/auth/passkeys/register/verify", scope: "admin", auth: "session", summary: "校验并登记 Passkey", body: auth.PasskeyRegisterRequest, response: auth.PasskeyInfo, }), passkeyRename: ep({ method: "patch", path: "/auth/passkeys/{id}", scope: "admin", auth: "session", summary: "重命名 Passkey", params: IdParam, body: auth.PasskeyRenameRequest, response: auth.PasskeyInfo, }), passkeyDelete: ep({ method: "delete", path: "/auth/passkeys/{id}", scope: "admin", auth: "session", summary: "删除 Passkey", params: IdParam, response: OkResult, }), passkeyLoginOptions: ep({ method: "post", path: "/auth/passkeys/login/options", scope: "read", auth: "public", summary: "Passkey 登录挑战(免鉴权)", response: auth.PasskeyChallenge, }), passkeyLogin: ep({ method: "post", path: "/auth/passkeys/login/verify", scope: "read", auth: "public", summary: "Passkey 登录(免鉴权;成功即下发会话)", body: auth.PasskeyLoginRequest, response: auth.LoginResult, }), }, // ── 事件 ── events: { list: ep({ method: "get", path: "/events", scope: "read", summary: "事件流:控制台用 SSE(text/event-stream,cookie 会话);API Key 调用者带 limit 取 JSON 补读页(after=上次 nextAfter)", query: events.EventReplayQuery, response: events.EventReplayPage, }), }, // ── 系统 ── system: { health: ep({ method: "get", path: "/health", scope: "read", summary: "服务健康状态(免鉴权)", response: HealthStatus, }), healthDetail: ep({ method: "get", path: "/system/health", scope: "admin", summary: "进程与健康:进程心跳 / 事件循环延迟 / SSE 连接 / DB / 磁盘 / 挂载根 / 引擎 / 定时任务", response: ops.SystemHealth, }), healthHistory: ep({ method: "get", path: "/system/health/history", scope: "admin", summary: "心跳采样时间序列(事件循环延迟、运行中 run、SSE 连接;按桶聚合)", query: ops.SystemHealthHistoryQuery, response: ops.SystemHealthHistory, }), storage: ep({ method: "get", path: "/system/storage", scope: "admin", summary: "存储:表体积前 15、库总体积、各缓存用量与预算、备份占用、DATA_DIR 分目录用量、清理任务状态", response: ops.SystemStorage, }), storageCleanup: ep({ method: "post", path: "/system/storage/cleanup/{target}", scope: "admin", summary: "立即执行某个存储清理任务(排入对应定时任务,已在排队或运行中则合并)", params: ops.StorageCleanupParam, response: ops.StorageCleanupResult, }), }, // ── 引擎面板(仅 admin) ── engine: { runs: ep({ method: "get", path: "/engine/runs", scope: "admin", summary: "引擎 runs(状态/名称/时间筛选,新→旧)", query: ops.EngineRunQuery, response: cursorPage(ops.EngineRun), }), run: ep({ method: "get", path: "/engine/runs/{id}", scope: "admin", summary: "run 详情(step attempts + signals)", params: EngineRunIdParam, response: ops.EngineRunDetail, }), stats: ep({ method: "get", path: "/engine/stats", scope: "admin", summary: "按状态计数 / 最老待处理 / 活跃 worker 快照", response: ops.EngineStats, }), cancel: ep({ method: "post", path: "/engine/runs/{id}/cancel", scope: "admin", summary: "取消 run", params: EngineRunIdParam, body: ops.EngineCancelRequest, response: ops.EngineCancelResult, }), retry: ep({ method: "post", path: "/engine/runs/{id}/retry", scope: "admin", summary: "按原任务名与输入重新入队(新幂等键 retry::<序号>)", params: EngineRunIdParam, response: z.object({ runId: z.string(), retryOf: z.string(), attempt: z.number().int().positive() }), }), }, } as const;