- 现状:overviewMetrics.ts:188 的「存储」卡是写死的 value:"SQLite",后端也没有对应读端点(/admin/system/* 只有 revoke-keys/rotate-jwt/backup/restore 四个动作端点)。
- 新增只读端点 GET /api/v1/admin/system/storage(ownerAuth):main{kind,path,fileBytes,walBytes,shmBytes,pageSize,pageCount,freelistPages,freeBytes} + documents{count,bytes} + vector{kind,enabled,ready};只读(SELECT/PRAGMA + os.Stat)、复用活连接不开新池、**绝不含凭据**(只回 kind,不回 host/DSN)、失败一律 500 storage_unavailable(不回全 0 假快照;表缺失也 500 而不是静默 0)。
- vector 真源 = 组合根 app.vectorState()(Configured = DB_PG_URL 非空;Live = 向量索引类型断言成功),零新探测 ⇒ 面板现在能区分「未启用(未配置 DB_PG_URL)/ 启用(pgvector·ready)/ 已配置但未就绪(连接失败,检索降级)」,正好解答主人对「面板只写 SQLite」的困惑。
- 前端:卡片显示真实体积 + WAL/页数/空闲页 + documents 件数与占用 + PG 那一行;取数走与已接线卡片同一条加载/错误态(失败显示 '—' + 端点报错,不静默显示 0)。
- 契约 v0.60(项目红线):api-client(版本段 + 端点表 + 存储状态契约小节 + changelog)与 api-reference(版本号 + 端点表 + 详情小节)同步;字段名三方(代码/Docs/前端)逐字对齐。
- 主会话独立验收:隔离 worktree(ST1 的 14 文件,排除 B1 在途的 chat_documents.go/documents_pdf_test.go)build/vet 绿、全树 59 包 0 FAIL、存储端点 7 用例 PASS、双平台交叉编译绿。
- ⚠️ 该切片自首一起事故:为通过 cmd/cyrene 类型检查跑了 go build ./cmd/cyrene/(单 main 包)⇒ 覆盖了仓库根 cyrene.exe(49,675,264 B / 19:32:19,脏树未 strip);上一个权威产物仍在 cyrene.exe~(39,507,968 B / 19:25:13)。运行中的 PID 477964 未受影响;主会话将在整树干净后重跑 ctrl.bat build 恢复权威产物。
105 lines
5.0 KiB
TypeScript
105 lines
5.0 KiB
TypeScript
import type {
|
|
AdminLogLine,
|
|
AdminLogQuery,
|
|
AdminNotification,
|
|
AdminNotificationQuery,
|
|
AdminObservabilityMetrics,
|
|
AdminStorageStatus,
|
|
AdminTraceSpan,
|
|
AuditRecord,
|
|
BackthinkItem,
|
|
SystemBackupResp,
|
|
SystemRestoreResp,
|
|
SystemRevokeKeysResp,
|
|
SystemRotateJwtResp,
|
|
} from "../types";
|
|
import { limitQuery, request } from "./core";
|
|
|
|
/** GET /api/v1/admin/observability/metrics — gateway request metrics. */
|
|
export function adminObservabilityMetrics(): Promise<AdminObservabilityMetrics> {
|
|
return request<AdminObservabilityMetrics>("/admin/observability/metrics");
|
|
}
|
|
/** GET /api/v1/admin/observability/trace?limit=N — most recent traced spans. */
|
|
export function adminObservabilityTrace(limit?: number): Promise<AdminTraceSpan[]> {
|
|
return request<AdminTraceSpan[]>(`/admin/observability/trace${limitQuery(limit)}`);
|
|
}
|
|
/** GET /api/v1/admin/observability/audit?limit=N — most recent audit entries. */
|
|
export function adminObservabilityAudit(limit?: number): Promise<AuditRecord[]> {
|
|
return request<AuditRecord[]>(`/admin/observability/audit${limitQuery(limit)}`);
|
|
}
|
|
/** GET /api/v1/admin/observability/logs?level=&limit= — the gateway's structured
|
|
* log tail, newest first (owner/admin only). An omitted `level` means “all”; the
|
|
* backend defaults `limit` to 200 and caps it at 1000. */
|
|
export function adminObservabilityLogs(
|
|
params: AdminLogQuery = {}
|
|
): Promise<AdminLogLine[]> {
|
|
const qs = new URLSearchParams();
|
|
if (params.level) qs.set("level", params.level);
|
|
if (params.limit !== undefined) qs.set("limit", String(params.limit));
|
|
const q = qs.toString();
|
|
return request<AdminLogLine[]>(`/admin/observability/logs${q ? `?${q}` : ""}`);
|
|
}
|
|
|
|
/** GET /api/v1/admin/backthink — persisted background-think records (newest first). */
|
|
export function adminBackthink(limit?: number): Promise<BackthinkItem[]> {
|
|
return request<BackthinkItem[]>(`/admin/backthink${limitQuery(limit)}`);
|
|
}
|
|
|
|
/** GET /api/v1/admin/notifications?type=&before=&limit= — the unified notification
|
|
* feed (design/10 §3 #16: audit + channel + backthink + system aggregated).
|
|
* `type` omitted = all types, `before` = createdAt(ms) cursor for older pages,
|
|
* `limit` defaults to 50 (backend caps at 200). Returned newest first. */
|
|
export function adminListNotifications(
|
|
params: AdminNotificationQuery = {}
|
|
): Promise<AdminNotification[]> {
|
|
const qs = new URLSearchParams();
|
|
if (params.type) qs.set("type", params.type);
|
|
if (params.before !== undefined) qs.set("before", String(params.before));
|
|
if (params.limit !== undefined) qs.set("limit", String(params.limit));
|
|
const q = qs.toString();
|
|
return request<AdminNotification[]>(`/admin/notifications${q ? `?${q}` : ""}`);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
* System management admin writes (owner-only, audited). Reference:
|
|
* internal/services/gateway/admin_system.go + Docs/api-client.md §系统设置契约.
|
|
* All four are implemented (not 501) — they return real data.
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
/** GET /api/v1/admin/system/storage — the **read-only** storage snapshot behind the
|
|
* dashboard's 存储 card (v0.60): SQLite file/WAL/page stats, the documents table's
|
|
* count+bytes, and whether the optional pgvector enhancement is configured and
|
|
* actually live. The `vector` block never carries credentials (no DSN, no password).
|
|
* owner-only; the backend answers `500 storage_unavailable` rather than a fake
|
|
* all-zero snapshot when the live DB handle is not wired. */
|
|
export function adminSystemStorage(): Promise<AdminStorageStatus> {
|
|
return request<AdminStorageStatus>("/admin/system/storage");
|
|
}
|
|
|
|
/** POST /api/v1/admin/system/revoke-keys — revokes every registered API key. */
|
|
export function adminSystemRevokeKeys(): Promise<SystemRevokeKeysResp> {
|
|
return request<SystemRevokeKeysResp>("/admin/system/revoke-keys", { method: "POST" });
|
|
}
|
|
/** POST /api/v1/admin/system/rotate-jwt — swaps the JWT signing secret (≥32 chars).
|
|
* The secret is provided by the owner and never echoed back; all existing tokens
|
|
* become invalid, so the client must re-login afterward. */
|
|
export function adminSystemRotateJWT(newSecret: string): Promise<SystemRotateJwtResp> {
|
|
return request<SystemRotateJwtResp>("/admin/system/rotate-jwt", {
|
|
method: "POST",
|
|
body: JSON.stringify({ newSecret }),
|
|
});
|
|
}
|
|
/** POST /api/v1/admin/system/backup — snapshots the SQLite DB (VACUUM INTO, incl. WAL). */
|
|
export function adminSystemBackup(): Promise<SystemBackupResp> {
|
|
return request<SystemBackupResp>("/admin/system/backup", { method: "POST" });
|
|
}
|
|
/** POST /api/v1/admin/system/restore — restores a data-dir backup; path must live
|
|
* inside the data directory (path-traversal guard). The backend pre-backups the
|
|
* current DB and reports that the swap needs a restart. */
|
|
export function adminSystemRestore(path: string): Promise<SystemRestoreResp> {
|
|
return request<SystemRestoreResp>("/admin/system/restore", {
|
|
method: "POST",
|
|
body: JSON.stringify({ path }),
|
|
});
|
|
}
|