fix(sites): 观众健康检查误报未登录,健康失败原因细分(HC-1)
观众 2026 版首页没有 <a href=logout>:退出按钮在 userbar 的 data-uploader-actions JSON 里由脚本渲染,旧登录标志 a[href*="logout"] 永远不命中,健康检查报"未登录"而用户数据正常。 - audiences 定义 loginSuccess 改为 #info_block / usercp.php / logout 任一(0.2.1) - 健康检查判定细分:登录页 / 权限页 / 疑似人机验证 / 找不到登录标志(带选择器), 原始判定写入 last_health.detail,站点概览技术信息展示 - 首页只是找不到登录标志时,以 24 小时内用户数据解析成功为已登录证据(同 MP) - 新 SiteError 类 challenge(cf-mitigated: challenge),不算凭据错误;健康状态 challenge - testkit mock 增加 setHeaderLayout;加观众首页脱敏结构样本的差分测试 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
322ecb2f8c
commit
b1d30bd6ee
16 files changed
+433
-27
No files matched your search
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { healthLine, healthMessage, healthTechItems } from "./site-fields.ts";
|
||||
|
||||
describe("健康检测文案(HC-1)", () => {
|
||||
it("人机验证单独成类;失败原因一句,原始判定进技术信息", () => {
|
||||
const h = {
|
||||
ok: false,
|
||||
status: "auth_expired",
|
||||
latencyMs: 120,
|
||||
message: '健康检查:首页里找不到登录标志(选择器 a[href*="logout"])',
|
||||
detail: { reason: "marker_missing", selector: 'a[href*="logout"]', path: "index.php", title: "Site :: 首页" },
|
||||
};
|
||||
expect(healthMessage(h)).toContain("找不到登录标志");
|
||||
expect(healthTechItems(h)).toEqual([
|
||||
{ label: "判定", value: "页面里找不到登录标志(marker_missing)" },
|
||||
{ label: "登录标志选择器", value: 'a[href*="logout"]', mono: true },
|
||||
{ label: "检测页面", value: "index.php", mono: true },
|
||||
{ label: "页面标题", value: "Site :: 首页" },
|
||||
]);
|
||||
expect(healthLine({ ok: false, status: "challenge", latencyMs: null, message: null }, null).text).toContain(
|
||||
"需要人机验证",
|
||||
);
|
||||
// 旧记录没有 detail
|
||||
expect(healthTechItems({ ok: true, status: "ok", latencyMs: 1, message: null })).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -134,6 +134,7 @@ const HEALTH_STATUS_TEXT: Record<string, string> = {
|
||||
unreachable: "连不上站点",
|
||||
site_maintenance: "站点维护中",
|
||||
parse_error: "页面解析失败",
|
||||
challenge: "需要人机验证",
|
||||
};
|
||||
|
||||
/** 健康检测一行:"正常 · 320 ms · 5 分钟前" / 失败原因一句 */
|
||||
@@ -151,6 +152,34 @@ export function healthMessage(h: SiteLastHealth | null): string | null {
|
||||
return h.message;
|
||||
}
|
||||
|
||||
const HEALTH_REASON_TEXT: Record<string, string> = {
|
||||
logged_in: "首页有登录标志",
|
||||
user_data: "首页没找到登录标志,按用户数据解析成功判为已登录",
|
||||
login_page: "返回登录页",
|
||||
permission_page: "返回权限提示页",
|
||||
challenge: "疑似人机验证",
|
||||
marker_missing: "页面里找不到登录标志",
|
||||
redirect_login: "跳转登录页",
|
||||
http: "HTTP 错误",
|
||||
};
|
||||
|
||||
/** 健康检测的原始判定 → 技术信息条目(判定、规则、选择器、页面标题、HTTP 状态) */
|
||||
export function healthTechItems(h: SiteLastHealth | null): { label: string; value: string; mono?: boolean }[] {
|
||||
const d = h?.detail;
|
||||
if (!d) return [];
|
||||
const str = (k: string) => (typeof d[k] === "string" || typeof d[k] === "number" ? String(d[k]) : "");
|
||||
const reason = str("reason");
|
||||
return [
|
||||
{ label: "判定", value: reason ? `${HEALTH_REASON_TEXT[reason] ?? reason}(${reason})` : "" },
|
||||
{ label: "命中规则", value: str("rule"), mono: true },
|
||||
{ label: "登录标志选择器", value: str("selector"), mono: true },
|
||||
{ label: "检测页面", value: str("path"), mono: true },
|
||||
{ label: "页面标题", value: str("title") },
|
||||
{ label: "HTTP 状态", value: str("httpStatus") },
|
||||
{ label: "用户数据时间", value: str("userDataAt") },
|
||||
].filter((it) => it.value);
|
||||
}
|
||||
|
||||
/** 限流:"20 次/分";空对象 = 默认 */
|
||||
export function rateLimitText(r: SiteRateLimit | null | undefined): string {
|
||||
if (!r || (!r.requestsPerMinute && !r.minIntervalMs && !r.concurrency)) return "默认";
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
|
||||
import { KvList } from "~/components/console/kv-list.tsx";
|
||||
import { StatTile } from "~/components/console/stat-tile.tsx";
|
||||
import { StatusBadge } from "~/components/console/status-badge.tsx";
|
||||
import { TechInfo } from "~/components/console/tech-info.tsx";
|
||||
import { relTime } from "~/components/workbench/release-fields.ts";
|
||||
import {
|
||||
ACCOUNT_OVERALL,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
hasLogin,
|
||||
healthLine,
|
||||
healthMessage,
|
||||
healthTechItems,
|
||||
kindLabel,
|
||||
overallStatus,
|
||||
primaryAccount,
|
||||
@@ -182,6 +184,7 @@ function AccountSiteOverview({ site }: { site: SiteListItem }) {
|
||||
{ label: "获取方式", value: capabilityText(site) },
|
||||
]}
|
||||
/>
|
||||
<TechInfo className="mt-2" columns={1} items={healthTechItems(site.lastHealth)} />
|
||||
</DetailSection>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user