import type { SiteFailureSample, SiteFailureSampleSummary } from "@umt/contracts"; import { Download } from "lucide-react"; import { useState } from "react"; import { useSiteFailureSample } from "~/api/sites.ts"; import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; import { KvList } from "~/components/console/kv-list.tsx"; import { Button } from "~/components/ui/button.tsx"; import { relTime } from "~/components/workbench/release-fields.ts"; import { failureSampleFileName, failureSampleItems, failureSampleText, failureSampleTitle } from "./site-fields.ts"; // 站点请求失败留样(技术信息里):最近几次「页面结构无法识别」/「人机验证」时站点返回的页面(已脱敏), // 用来分清是站点改版还是被人机验证拦了。列表不带正文,展开时再取。 function download(s: SiteFailureSample) { const url = URL.createObjectURL(new Blob([failureSampleText(s)], { type: "text/plain;charset=utf-8" })); const a = document.createElement("a"); a.href = url; a.download = failureSampleFileName(s); a.click(); URL.revokeObjectURL(url); } function SampleBody({ siteId, sampleId }: { siteId: string; sampleId: string }) { const q = useSiteFailureSample(siteId, sampleId, true); if (q.isPending) return ; if (q.error) return void q.refetch()} />; return ( download(q.data)}> 下载文本 {q.data.body || "(页面是空的)"} ); } /** 一份样本:标题一行,展开看请求信息与页面 */ export function FailureSampleRow({ siteId, sample }: { siteId: string; sample: SiteFailureSampleSummary }) { const [open, setOpen] = useState(false); return ( setOpen(!open)} className="flex w-full flex-wrap items-baseline justify-between gap-x-3 text-left text-sm text-foreground" > {failureSampleTitle(sample)} {relTime(sample.createdAt)} · {open ? "收起" : "查看"} {open ? ( ) : null} ); } /** 最近的失败样本(列表由调用方用 useSiteFailureSamples 取);没有就不渲染 */ export function SiteFailureSamples({ siteId, items }: { siteId: string; items: SiteFailureSampleSummary[] }) { if (!items.length) return null; return ( 最近的失败页面(已脱敏,最多保留 5 份) {items.map((s) => ( ))} ); }
{q.data.body || "(页面是空的)"}