20260718 重构 RPKI Explorer 查询界面

This commit is contained in:
2026-07-18 10:38:38 +08:00
parent a77982deaa
commit a5902c0a5e
79 changed files with 7964 additions and 4861 deletions

View File

@@ -0,0 +1,308 @@
/**
* Zod schemas describing the rpki_query_service API contract.
*
* Schemas are intentionally `.passthrough()` so additive backend changes do
* not break the UI; fields the UI depends on are typed strictly.
*/
import { z } from "zod";
/* ------------------------------------------------------------------ */
/* Records */
/* ------------------------------------------------------------------ */
export const runCountsSchema = z
.object({
publicationPoints: z.number().optional(),
objects: z.number().optional(),
freshObjects: z.number().optional(),
cachedObjects: z.number().optional(),
rejectedObjects: z.number().optional(),
freshRejectedObjects: z.number().optional(),
cachedRejectedObjects: z.number().optional(),
trustAnchors: z.number().optional(),
vrps: z.number().optional(),
aspas: z.number().optional(),
warnings: z.number().optional(),
})
.passthrough();
export const runRecordSchema = z
.object({
schemaVersion: z.number().optional(),
runId: z.string(),
runSeq: z.number().nullish(),
runDir: z.string().optional(),
validationTime: z.string().nullish(),
syncMode: z.string().nullish(),
startedAt: z.string().nullish(),
finishedAt: z.string().nullish(),
wallMs: z.number().nullish(),
maxRssKb: z.number().nullish(),
artifactPaths: z.record(z.string()).optional(),
counts: runCountsSchema.optional(),
indexStatus: z.string().optional(),
indexError: z.string().nullish(),
})
.passthrough();
export const repositoryRecordSchema = z
.object({
repoId: z.string(),
uri: z.string(),
host: z.string(),
transport: z.string().nullish(),
publicationPoints: z.number().nullish(),
objects: z.number().nullish(),
rejectedObjects: z.number().nullish(),
downloadBytes: z.number().nullish(),
syncDurationMsTotal: z.number().nullish(),
phases: z.record(z.number()).optional(),
terminalStates: z.record(z.number()).optional(),
})
.passthrough();
export const publicationPointRecordSchema = z
.object({
ppId: z.string(),
repoId: z.string(),
nodeId: z.number().nullish(),
parentNodeId: z.number().nullish(),
rsyncBaseUri: z.string().nullish(),
manifestRsyncUri: z.string().nullish(),
publicationPointRsyncUri: z.string().nullish(),
rrdpNotificationUri: z.string().nullish(),
source: z.string().nullish(),
repoSyncSource: z.string().nullish(),
repoSyncPhase: z.string().nullish(),
repoSyncDurationMs: z.number().nullish(),
repoSyncError: z.string().nullish(),
repoTerminalState: z.string().nullish(),
thisUpdate: z.string().nullish(),
nextUpdate: z.string().nullish(),
verifiedAt: z.string().nullish(),
objects: z.number().nullish(),
rejectedObjects: z.number().nullish(),
warnings: z.number().nullish(),
})
.passthrough();
export const objectInstanceRecordSchema = z
.object({
objectInstanceId: z.string(),
uri: z.string(),
uriHash: z.string().optional(),
sha256: z.string().optional(),
objectType: z.string(),
result: z.string().nullish(),
detailSummary: z.string().nullish(),
repoId: z.string(),
ppId: z.string(),
sourceSection: z.string().nullish(),
rejected: z.boolean().optional(),
rejectReason: z.string().nullish(),
sizeBytes: z.number().nullish(),
rawAvailable: z.boolean().optional(),
})
.passthrough();
export const chainEdgeRecordSchema = z
.object({
relation: z.string(),
fromUri: z.string().nullish(),
toUri: z.string().nullish(),
toObjectInstanceId: z.string().nullish(),
toSha256: z.string().nullish(),
status: z.string().nullish(),
evidence: z.unknown().optional(),
})
.passthrough();
/** Sparse record returned by /objects/by-uri (index hit, no lazy scan). */
export const objectUriIndexRecordSchema = z
.object({
uri: z.string(),
sha256: z.string(),
objectInstanceId: z.string(),
repoId: z.string(),
ppId: z.string(),
})
.passthrough();
export const exportJobRecordSchema = z
.object({
jobId: z.string(),
runId: z.string(),
scope: z.string(),
repoId: z.string().nullish(),
ppId: z.string().nullish(),
status: z.string(),
createdAt: z.string().nullish(),
finishedAt: z.string().nullish(),
outputPath: z.string().nullish(),
objectCount: z.number().nullish(),
bytesWritten: z.number().nullish(),
error: z.string().nullish(),
})
.passthrough();
export const projectionRecordSchema = z
.object({
schemaVersion: z.number().optional(),
sha256: z.string().optional(),
objectType: z.string().optional(),
parseStatus: z.string().optional(),
errorSummary: z.string().nullish(),
projection: z.record(z.unknown()).nullish(),
})
.passthrough();
export const validationIssueSchema = z
.object({
severity: z.string().nullish(),
reasonCode: z.string().nullish(),
reasonText: z.string().nullish(),
rfcRefs: z.array(z.string()).optional(),
evidence: z.unknown().optional(),
})
.passthrough();
export const validationStageSchema = z
.object({
status: z.string().nullish(),
stage: z.string().nullish(),
mode: z.string().nullish(),
projectionStatus: z.string().nullish(),
issues: z.array(validationIssueSchema).optional(),
detailSummary: z.string().nullish(),
edgesCount: z.number().nullish(),
note: z.string().nullish(),
})
.passthrough();
export const validationSummarySchema = z
.object({
finalStatus: z.string().nullish(),
auditResult: z.string().nullish(),
detailSummary: z.string().nullish(),
parsevalidate: validationStageSchema.nullish(),
chainvalidate: validationStageSchema.nullish(),
})
.passthrough();
export const explainRecordSchema = z
.object({
finalStatus: z.string().nullish(),
auditResult: z.string().nullish(),
authoritative: z.boolean().optional(),
explainMode: z.string().nullish(),
parsevalidate: validationStageSchema.nullish(),
chainvalidate: validationStageSchema.nullish(),
chainEdges: z.array(chainEdgeRecordSchema).optional(),
})
.passthrough();
export const searchResultSchema = z
.object({
objects: z.array(objectInstanceRecordSchema),
repos: z.array(repositoryRecordSchema),
publicationPoints: z.array(publicationPointRecordSchema),
})
.passthrough();
export const healthSchema = z
.object({
status: z.string(),
service: z.string().optional(),
version: z.number().optional(),
latestReadyRun: z.string().nullish(),
})
.passthrough();
export const serviceInfoSchema = z
.object({
service: z.string(),
version: z.number(),
})
.passthrough();
export const manifestFileEntrySchema = z
.object({
fileName: z.string(),
hashHex: z.string(),
})
.passthrough();
export const revokedCertEntrySchema = z
.object({
serialNumberHex: z.string().nullish(),
serialNumber: z.string().nullish(),
revocationDate: z.string().nullish(),
})
.passthrough();
export const runSummarySchema = z
.object({
publicationPoints: z.number().optional(),
objects: z.number().optional(),
repos: z.number().optional(),
vrps: z.number().optional(),
aspas: z.number().optional(),
})
.passthrough();
export const statsMapSchema = z.record(z.number());
/* ------------------------------------------------------------------ */
/* Envelope */
/* ------------------------------------------------------------------ */
const pageSchema = z.object({
nextCursor: z.string().nullable(),
limit: z.number(),
});
const metaSchema = z
.object({
runId: z.string().nullish(),
schemaVersion: z.number().optional(),
})
.passthrough();
export function envelopeSchema<T extends z.ZodTypeAny>(dataSchema: T) {
return z
.object({
data: dataSchema,
page: pageSchema.nullish(),
meta: metaSchema.optional(),
})
.passthrough();
}
/* ------------------------------------------------------------------ */
/* Inferred types */
/* ------------------------------------------------------------------ */
export type RunCounts = z.infer<typeof runCountsSchema>;
export type RunRecord = z.infer<typeof runRecordSchema>;
export type RepositoryRecord = z.infer<typeof repositoryRecordSchema>;
export type PublicationPointRecord = z.infer<typeof publicationPointRecordSchema>;
export type ObjectInstanceRecord = z.infer<typeof objectInstanceRecordSchema>;
export type ChainEdgeRecord = z.infer<typeof chainEdgeRecordSchema>;
export type ExportJobRecord = z.infer<typeof exportJobRecordSchema>;
export type ProjectionRecord = z.infer<typeof projectionRecordSchema>;
export type ValidationIssue = z.infer<typeof validationIssueSchema>;
export type ValidationStage = z.infer<typeof validationStageSchema>;
export type ValidationSummary = z.infer<typeof validationSummarySchema>;
export type ExplainRecord = z.infer<typeof explainRecordSchema>;
export type SearchResult = z.infer<typeof searchResultSchema>;
export type HealthStatus = z.infer<typeof healthSchema>;
export type ServiceInfo = z.infer<typeof serviceInfoSchema>;
export type ManifestFileEntry = z.infer<typeof manifestFileEntrySchema>;
export type RevokedCertEntry = z.infer<typeof revokedCertEntrySchema>;
export type RunSummary = z.infer<typeof runSummarySchema>;
export interface ListResult<T> {
items: T[];
nextCursor: string | null;
runId: string | undefined;
}