feat(web-sentinel): add dashboard manual trigger
This commit is contained in:
@@ -537,6 +537,7 @@ export function probeSentinelDashboardBrowser(state: SentinelCicdState, options:
|
||||
`export UNIDESK_SENTINEL_DASHBOARD_URL=${shellQuote(dashboardUrl)}`,
|
||||
`export UNIDESK_SENTINEL_DASHBOARD_SCREENSHOT="$UNIDESK_WEB_PROBE_ARTIFACT_REMOTE_DIR"/${shellQuote(screenshotName)}`,
|
||||
`export UNIDESK_SENTINEL_DASHBOARD_CAPTURE=${shellQuote(options.action === "screenshot" ? "1" : "0")}`,
|
||||
`export UNIDESK_SENTINEL_DASHBOARD_TRIGGER=${shellQuote(options.action === "trigger" ? "1" : "0")}`,
|
||||
`export UNIDESK_SENTINEL_DASHBOARD_WIDTH=${shellQuote(widthRaw ?? "1440")}`,
|
||||
`export UNIDESK_SENTINEL_DASHBOARD_HEIGHT=${shellQuote(heightRaw ?? "900")}`,
|
||||
`export UNIDESK_SENTINEL_DASHBOARD_TIMEOUT_MS=${shellQuote(String(options.timeoutMs))}`,
|
||||
@@ -633,6 +634,7 @@ const { chromium } = await import(playwrightModuleSpecifier);
|
||||
const url = process.env.UNIDESK_SENTINEL_DASHBOARD_URL;
|
||||
const screenshotPath = process.env.UNIDESK_SENTINEL_DASHBOARD_SCREENSHOT || "";
|
||||
const captureScreenshot = process.env.UNIDESK_SENTINEL_DASHBOARD_CAPTURE === "1";
|
||||
const triggerManual = process.env.UNIDESK_SENTINEL_DASHBOARD_TRIGGER === "1";
|
||||
const width = Number(process.env.UNIDESK_SENTINEL_DASHBOARD_WIDTH || 1440);
|
||||
const height = Number(process.env.UNIDESK_SENTINEL_DASHBOARD_HEIGHT || 900);
|
||||
const timeout = Number(process.env.UNIDESK_SENTINEL_DASHBOARD_TIMEOUT_MS || 30000);
|
||||
@@ -709,7 +711,32 @@ if (requestedRunId) {
|
||||
await page.waitForTimeout(150);
|
||||
}
|
||||
|
||||
const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId, requestedRunId, requestedRunSelection }) => {
|
||||
let manualTrigger = { requested: triggerManual, ok: !triggerManual, status: triggerManual ? "not-attempted" : "not-requested", jobName: null, statusText: "" };
|
||||
if (triggerManual) {
|
||||
manualTrigger = await page.evaluate(async () => {
|
||||
const wait = (ms) => new Promise((resolve) => window.setTimeout(resolve, ms));
|
||||
const root = document.querySelector("[data-monitor-shell='true']");
|
||||
const button = document.querySelector("[data-monitor-manual-trigger='true']");
|
||||
if (!(button instanceof HTMLButtonElement)) return { requested: true, ok: false, status: "button-missing", jobName: null, statusText: "" };
|
||||
if (button.disabled) return { requested: true, ok: false, status: "button-disabled", jobName: button.getAttribute("data-trigger-job") || null, statusText: "" };
|
||||
button.click();
|
||||
for (let index = 0; index < 160; index += 1) {
|
||||
const state = root?.getAttribute("data-manual-trigger-state") || button.getAttribute("data-trigger-state") || "";
|
||||
const jobName = root?.getAttribute("data-manual-trigger-job") || button.getAttribute("data-trigger-job") || null;
|
||||
const statusText = String(document.querySelector("[data-monitor-manual-trigger-status='true']")?.textContent || "").replace(/\s+/g, " ").trim();
|
||||
if (state === "triggered" || state === "already-active" || state === "failed") {
|
||||
return { requested: true, ok: state === "triggered" || state === "already-active", status: state, jobName, statusText };
|
||||
}
|
||||
await wait(250);
|
||||
}
|
||||
const finalState = root?.getAttribute("data-manual-trigger-state") || button.getAttribute("data-trigger-state") || "timeout";
|
||||
const finalJob = root?.getAttribute("data-manual-trigger-job") || button.getAttribute("data-trigger-job") || null;
|
||||
return { requested: true, ok: false, status: finalState || "timeout", jobName: finalJob, statusText: "timeout" };
|
||||
}).catch((error) => ({ requested: true, ok: false, status: "trigger-error", jobName: null, statusText: String(error?.message || error).slice(0, 300) }));
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
|
||||
const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId, requestedRunId, requestedRunSelection, manualTrigger }) => {
|
||||
const numberValue = (value) => Number.isFinite(Number(value)) ? Number(value) : 0;
|
||||
const objectOrNull = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
||||
const text = (selector) => String(document.querySelector(selector)?.textContent || "").replace(/\s+/g, " ").trim();
|
||||
@@ -720,6 +747,8 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
|
||||
const trendErrorCurve = document.querySelector("[data-monitor-trend-error-curve='true']");
|
||||
const trendWarningCurve = document.querySelector("[data-monitor-trend-warning-curve='true']");
|
||||
const memoryChart = document.querySelector("[data-run-memory-chart='true']");
|
||||
const manualTriggerButton = document.querySelector("[data-monitor-manual-trigger='true']");
|
||||
const manualTriggerStatus = document.querySelector("[data-monitor-manual-trigger-status='true']");
|
||||
const chartTiming = {
|
||||
hasDurationCurve: Boolean(trend),
|
||||
hasErrorCurve: Boolean(trendErrorCurve),
|
||||
@@ -884,6 +913,16 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
|
||||
requestMatched: !requestedRunId || targetRunId === requestedRunId,
|
||||
},
|
||||
requestedRunSelection,
|
||||
manualTriggerUi: {
|
||||
requested: manualTrigger.requested,
|
||||
ok: manualTrigger.ok,
|
||||
status: manualTrigger.status,
|
||||
jobName: manualTrigger.jobName,
|
||||
buttonPresent: Boolean(manualTriggerButton),
|
||||
buttonDisabled: manualTriggerButton instanceof HTMLButtonElement ? manualTriggerButton.disabled : null,
|
||||
buttonState: manualTriggerButton?.getAttribute("data-trigger-state") || null,
|
||||
statusText: String(manualTriggerStatus?.textContent || "").replace(/\s+/g, " ").trim(),
|
||||
},
|
||||
chartTiming,
|
||||
memorySummary,
|
||||
api: {
|
||||
@@ -901,7 +940,7 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
|
||||
overflow: overflow.slice(0, 2),
|
||||
},
|
||||
};
|
||||
}, { expectedRoutePrefix, expectedSentinelId, requestedRunId, requestedRunSelection });
|
||||
}, { expectedRoutePrefix, expectedSentinelId, requestedRunId, requestedRunSelection, manualTrigger });
|
||||
|
||||
if (captureScreenshot && screenshotPath) {
|
||||
if (requestedRunId && dom.memorySummary?.present === true && dom.memorySummary?.matchesTargetRun === true) {
|
||||
@@ -932,6 +971,7 @@ const ok = navigationOk
|
||||
&& dom.sentinelBoundary?.routePrefixMatches === true
|
||||
&& dom.errorVisible !== true
|
||||
&& dom.requestedRunSelection?.ok === true
|
||||
&& manualTrigger.ok === true
|
||||
&& dom.chartTiming?.ok === true
|
||||
&& dom.memorySummary?.contractOk === true
|
||||
&& dom.layout?.horizontalOverflow !== true
|
||||
@@ -946,6 +986,7 @@ console.log("__WEB_PROBE_SENTINEL_DASHBOARD_JSON__" + JSON.stringify({
|
||||
executablePath: executablePath || null,
|
||||
viewport: { width, height },
|
||||
screenshotPath: captureScreenshot ? screenshotPath : null,
|
||||
manualTrigger,
|
||||
dom,
|
||||
consoleCount: consoleMessages.length,
|
||||
consoleErrorCount: consoleErrors.length,
|
||||
@@ -1006,6 +1047,8 @@ function dashboardDegradedReason(result: CommandResult, transport: Record<string
|
||||
return "sentinel-dashboard-transport-failed";
|
||||
}
|
||||
if (page === null) return "sentinel-dashboard-browser-output-missing";
|
||||
const manualTrigger = record(page.manualTrigger);
|
||||
if (manualTrigger.requested === true && manualTrigger.ok !== true) return "sentinel-dashboard-manual-trigger-failed";
|
||||
if (page.ok !== true) return "sentinel-dashboard-render-failed";
|
||||
if (!screenshotOk) return "sentinel-dashboard-screenshot-missing";
|
||||
return "sentinel-dashboard-unknown";
|
||||
@@ -1415,6 +1458,8 @@ function renderDashboardResult(result: Record<string, unknown>): string {
|
||||
const chartTiming = record(dom.chartTiming);
|
||||
const memorySummary = record(dom.memorySummary);
|
||||
const requestedRunSelection = record(dom.requestedRunSelection);
|
||||
const manualTrigger = record(page.manualTrigger);
|
||||
const manualTriggerUi = record(dom.manualTriggerUi);
|
||||
const screenshot = record(result.screenshot);
|
||||
const remote = record(result.remote);
|
||||
const transport = record(result.transport);
|
||||
@@ -1464,6 +1509,16 @@ function renderDashboardResult(result: Record<string, unknown>): string {
|
||||
chartTiming.hasWarningCurve ?? "-",
|
||||
]]),
|
||||
"",
|
||||
manualTrigger.requested === true
|
||||
? table(["TRIGGER", "STATUS", "JOB", "UI_STATE", "UI_TEXT"], [[
|
||||
manualTrigger.ok ?? "-",
|
||||
manualTrigger.status ?? "-",
|
||||
manualTrigger.jobName ?? "-",
|
||||
manualTriggerUi.buttonState ?? "-",
|
||||
manualTriggerUi.statusText ?? "-",
|
||||
]])
|
||||
: "TRIGGER\n-",
|
||||
"",
|
||||
table(["MEMORY_CHART", "MEMORY_RUN", "MEMORY_MATCH", "MEMORY_PAGES", "MEMORY_SAMPLES", "API_PAGES", "API_SAMPLES", "CONTRACT", "STATUS", "MEMORY_SOURCE"], [[
|
||||
memorySummary.present ?? "-",
|
||||
memorySummary.runId ?? "-",
|
||||
@@ -1491,6 +1546,7 @@ function renderDashboardResult(result: Record<string, unknown>): string {
|
||||
degradedReason === null ? "BLOCKER\n-" : table(["CODE", "REMOTE_EXIT", "TRANSPORT", "POLL_PHASE"], [[degradedReason, remote.exitCode, transport.ok, pollFailure.phase ?? "-"]]),
|
||||
"",
|
||||
"NEXT",
|
||||
` trigger: bun scripts/cli.ts web-probe sentinel dashboard trigger --node ${result.node} --lane ${result.lane} --sentinel ${result.sentinelId}`,
|
||||
` screenshot: bun scripts/cli.ts web-probe sentinel dashboard screenshot --node ${result.node} --lane ${result.lane} --sentinel ${result.sentinelId}`,
|
||||
` validate: bun scripts/cli.ts web-probe sentinel validate --node ${result.node} --lane ${result.lane} --sentinel ${result.sentinelId}`,
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user