feat(web): 站点页技术信息里显示最近的失败页面样本

站点概览「站点」一栏的技术信息里列出最近 5 份失败样本(搜索 / 列表 / 详情 · 人机验证 / 页面结构无法识别 ·
HTTP 状态),展开看脱敏地址、错误、命中规则、关键响应头和页面正文,可下载文本;没有样本不显示。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 15:43:15 +08:00
1 parent bd475a4ce1
commit ec8b224482
5 files changed
+228 -2

No files matched your search

+31
View File
@@ -9,6 +9,8 @@ import {
type SiteCredentialsBody,
SiteCredentialsResult,
SiteDefinitionView,
SiteFailureSample,
SiteFailureSampleSummary,
SiteHealthResult,
SiteListItem,
SiteRefreshLoginAccepted,
@@ -35,6 +37,8 @@ export const siteKeys = {
accounts: (siteId?: string) => ["sites", "accounts", siteId ?? "all"] as const,
snapshots: (siteId: string) => ["sites", "snapshots", siteId] as const,
requestStats: (siteId: string) => ["sites", "request-stats", siteId] as const,
failureSamples: (siteId: string) => ["sites", "failure-samples", siteId] as const,
failureSample: (siteId: string, sampleId: string) => ["sites", "failure-samples", siteId, sampleId] as const,
definitions: () => ["sites", "definitions"] as const,
userData: (siteId: string, accountId?: string, days = 30) =>
["sites", "user-data", siteId, accountId ?? "first", days] as const,
@@ -105,6 +109,33 @@ export function useSiteRequestStats(siteId: string | undefined) {
});
}
/** 最近的失败留样(页面结构无法识别 / 人机验证;每站最近 5 份,不带正文) */
export function useSiteFailureSamples(siteId: string | undefined) {
return useQuery({
queryKey: siteKeys.failureSamples(siteId ?? ""),
enabled: !!siteId,
queryFn: ({ signal }) =>
apiParsed(z.object({ items: z.array(SiteFailureSampleSummary) }), "/api/v1/sites/:id/failure-samples", {
pathParams: { id: siteId ?? "" },
signal,
}),
});
}
/** 一份失败留样(带脱敏后的页面正文);展开时才取 */
export function useSiteFailureSample(siteId: string, sampleId: string, enabled: boolean) {
return useQuery({
queryKey: siteKeys.failureSample(siteId, sampleId),
enabled,
staleTime: Number.POSITIVE_INFINITY,
queryFn: ({ signal }) =>
apiParsed(SiteFailureSample, "/api/v1/sites/:id/failure-samples/:sampleId", {
pathParams: { id: siteId, sampleId },
signal,
}),
});
}
/** 访问默认的一句话说明:"快照每 60 分钟 1 页 · 健康检测每 6 小时 · 详情缓存 24 小时" */
export function accessSummary(s: SiteRequestStats): string {
const dur = (sec: number) => (sec % 3600 === 0 ? `${sec / 3600} 小时` : `${Math.round(sec / 60)} 分钟`);