fix: restore web sentinel cadence and monitor hover

This commit is contained in:
Codex
2026-06-27 07:28:31 +00:00
parent a02fa6f6e5
commit 9ffb781c8b
5 changed files with 775 additions and 15 deletions
@@ -3,6 +3,7 @@
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-26-p9-multi-web-probe-sentinel.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-26-p10-monitor-web-aggregation.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-27-p11-monitor-web-observability-dashboard.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-27-p12-cadence-scheduler-monitor-web.
// Responsibility: YAML-first CI/CD, image, GitOps and Argo command plan for the web-probe sentinel.
import { createHash, randomUUID } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
@@ -1999,6 +2000,24 @@ for (let attempt = 1; attempt <= maxNavigationAttempts; attempt += 1) {
await page.waitForTimeout(750 * attempt);
}
await page.evaluate(() => {
const detailPane = document.querySelector(".workspace-grid .pane-detail");
if (detailPane instanceof HTMLElement) detailPane.scrollTop = Math.min(96, Math.max(0, detailPane.scrollHeight - detailPane.clientHeight));
}).catch(() => {});
await page.waitForTimeout(150);
const trendHoverPoint = await page.evaluate(() => {
const target = document.querySelector(".trend-dot-hit .trend-dot-red") || document.querySelector(".trend-dot-hit .trend-dot-warning");
if (!(target instanceof SVGElement)) return null;
const rect = target.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) return null;
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
}).catch(() => null);
if (trendHoverPoint) {
await page.mouse.move(trendHoverPoint.x, trendHoverPoint.y);
await page.waitForTimeout(250);
}
if (captureScreenshot && screenshotPath) {
await page.screenshot({ path: screenshotPath, fullPage, animations: "disabled" }).catch((error) => {
pageErrors.push({ message: "screenshot failed: " + String(error?.message || error).slice(0, 400) });
@@ -2012,9 +2031,12 @@ const dom = await page.evaluate(() => {
const shell = document.querySelector("[data-monitor-shell='true']");
const error = document.querySelector("#monitor-web-error");
const trend = document.querySelector("[data-monitor-trend-curve]");
const trendTooltip = document.querySelector("[data-monitor-trend-tooltip='true']");
const timeline = document.querySelector("[data-monitor-timeline='true']");
const workspace = document.querySelector("[data-monitor-independent-scroll='true']");
const panes = Array.from(document.querySelectorAll(".workspace-grid .pane"));
const detailPane = document.querySelector(".workspace-grid .pane-detail");
const detailHeader = document.querySelector("#monitor-web-root > div > section.workspace-grid > main > div.pane-header");
const doc = document.documentElement;
const body = document.body;
const viewport = { width: window.innerWidth, height: window.innerHeight };
@@ -2063,6 +2085,8 @@ const dom = await page.evaluate(() => {
runRows: document.querySelectorAll(".run-list .run-row").length,
findingItems: document.querySelectorAll(".finding-list .finding-card").length,
trendCurve: Boolean(trend),
trendDotCount: document.querySelectorAll(".trend-dot-hit").length,
trendTooltip: tooltipSummary(trendTooltip),
trendPanelText: text("#trend-heading"),
timelineItems: document.querySelectorAll(".timeline-list .timeline-item").length,
timelineVisible: Boolean(timeline),
@@ -2089,6 +2113,7 @@ const dom = await page.evaluate(() => {
const style = window.getComputedStyle(pane);
return style.overflowY === "auto" || style.overflowY === "scroll";
}),
stickyHeader: stickyHeaderSummary(detailPane, detailHeader),
},
layout: {
viewport,
@@ -2098,6 +2123,46 @@ const dom = await page.evaluate(() => {
overflow,
},
};
function tooltipSummary(element) {
const body = String(element?.textContent || "").replace(/\s+/g, " ").trim();
return {
visible: Boolean(element && body.length > 0),
text: body.slice(0, 240),
hasValues: /红色\s+\d+/u.test(body) && /警告\s+\d+/u.test(body) && /总量\s+\d+/u.test(body),
hasTime: /UTC/u.test(body) || /\d{4}-\d{2}-\d{2}/u.test(body),
};
}
function stickyHeaderSummary(pane, header) {
if (!(pane instanceof HTMLElement) || !(header instanceof HTMLElement)) {
return { present: false, coversScroll: false, backgroundOpaque: false, detailScrollTop: null };
}
const rect = header.getBoundingClientRect();
const style = window.getComputedStyle(header);
const sampleX = Math.round(rect.left + Math.min(32, Math.max(2, rect.width / 2)));
const sampleY = Math.round(rect.top + Math.min(12, Math.max(2, rect.height / 2)));
const topElement = document.elementFromPoint(sampleX, sampleY);
return {
present: true,
detailScrollTop: pane.scrollTop,
headerTop: Math.round(rect.top),
headerBottom: Math.round(rect.bottom),
zIndex: style.zIndex,
backgroundColor: style.backgroundColor,
coversScroll: Boolean(topElement && header.contains(topElement)),
backgroundOpaque: backgroundIsOpaque(style.backgroundColor),
topElementClass: String(topElement?.className || "").slice(0, 80),
};
}
function backgroundIsOpaque(value) {
const rgba = /rgba?\(([^)]+)\)/u.exec(value);
if (rgba === null) return value.length > 0 && value !== "transparent";
const parts = rgba[1].split(",").map((part) => part.trim());
if (parts.length < 4) return true;
return Number(parts[3]) >= 0.99;
}
});
const consoleErrors = consoleMessages.filter((item) => item.type === "error");
@@ -2109,8 +2174,12 @@ const ok = !navigationError
&& dom.ready === true
&& dom.errorVisible !== true
&& dom.trendCurve === true
&& (dom.trendDotCount === 0 || (dom.trendTooltip?.visible === true && dom.trendTooltip?.hasValues === true && dom.trendTooltip?.hasTime === true))
&& dom.timelineVisible === true
&& dom.scrollModel?.independentScroll === true
&& dom.scrollModel?.stickyHeader?.present === true
&& dom.scrollModel?.stickyHeader?.coversScroll === true
&& dom.scrollModel?.stickyHeader?.backgroundOpaque === true
&& dom.layout?.horizontalOverflow !== true
&& pageErrors.length === 0;