243 lines
8.2 KiB
JavaScript
243 lines
8.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* UI-level diagnosis for motor console (no backend-side health APIs as truth).
|
|
* Launches Edge against :2625/pages/motor.html, dumps what the page SHOWS.
|
|
* Usage: node scripts/ui_diag_motor.mjs [port]
|
|
*/
|
|
import { spawn } from "node:child_process";
|
|
import { writeFileSync, mkdirSync } from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const PORT = Number(process.argv[2] || 2625);
|
|
const PAGE = `http://127.0.0.1:${PORT}/pages/motor.html`;
|
|
const CDP = "http://127.0.0.1:9333";
|
|
const OUT = path.resolve("output/ui_diag");
|
|
mkdirSync(OUT, { recursive: true });
|
|
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
async function findWs() {
|
|
for (let i = 0; i < 40; i++) {
|
|
try {
|
|
const list = await (await fetch(CDP + "/json/list")).json();
|
|
const page = list.find((t) => t.type === "page" && t.url.includes("motor"));
|
|
if (page && page.webSocketDebuggerUrl) return page.webSocketDebuggerUrl;
|
|
const any = list.find((t) => t.type === "page");
|
|
if (any && any.webSocketDebuggerUrl) return any.webSocketDebuggerUrl;
|
|
} catch (_) {}
|
|
await sleep(250);
|
|
}
|
|
throw new Error("CDP not available on 9333");
|
|
}
|
|
|
|
const edgeCandidates = [
|
|
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
];
|
|
|
|
function launchEdge() {
|
|
const exe = edgeCandidates.find((p) => p && require("fs").existsSync(p)) || "msedge.exe";
|
|
// prefer spawn without require - use existsSync from fs
|
|
return spawn(
|
|
edgeCandidates.find((p) => {
|
|
try {
|
|
return require("node:fs").existsSync(p);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}) || "msedge.exe",
|
|
[
|
|
"--headless=new",
|
|
"--disable-gpu",
|
|
`--remote-debugging-port=9333`,
|
|
"--user-data-dir=" + path.join(OUT, "edge-profile"),
|
|
"--no-first-run",
|
|
"--no-default-browser-check",
|
|
PAGE,
|
|
],
|
|
{ stdio: "ignore", detached: true }
|
|
);
|
|
}
|
|
|
|
import { existsSync } from "node:fs";
|
|
|
|
function launchEdge2() {
|
|
const exe =
|
|
edgeCandidates.find((p) => existsSync(p)) || "msedge.exe";
|
|
const child = spawn(
|
|
exe,
|
|
[
|
|
"--headless=new",
|
|
"--disable-gpu",
|
|
"--remote-debugging-port=9333",
|
|
`--user-data-dir=${path.join(OUT, "edge-profile")}`,
|
|
"--no-first-run",
|
|
"--no-default-browser-check",
|
|
PAGE,
|
|
],
|
|
{ stdio: "ignore", detached: true }
|
|
);
|
|
child.unref();
|
|
return child;
|
|
}
|
|
|
|
const url = await (async () => {
|
|
try {
|
|
await fetch(CDP + "/json/list");
|
|
} catch {
|
|
console.log("launching Edge headless →", PAGE);
|
|
launchEdge2();
|
|
await sleep(2000);
|
|
}
|
|
return findWs();
|
|
})();
|
|
|
|
console.log("CDP", url);
|
|
const sock = new WebSocket(url);
|
|
await new Promise((r, j) => {
|
|
sock.onopen = r;
|
|
sock.onerror = j;
|
|
});
|
|
let id = 0;
|
|
const pending = new Map();
|
|
const consoleMsgs = [];
|
|
sock.onmessage = (ev) => {
|
|
const m = JSON.parse(ev.data);
|
|
if (m.id && pending.has(m.id)) {
|
|
pending.get(m.id)(m);
|
|
pending.delete(m.id);
|
|
}
|
|
if (m.method === "Runtime.consoleAPICalled") {
|
|
const args = (m.params.args || [])
|
|
.map((a) => a.value ?? a.description ?? a.unserializableValue ?? "")
|
|
.join(" ");
|
|
consoleMsgs.push(`[${m.params.type}] ${args}`);
|
|
}
|
|
if (m.method === "Runtime.exceptionThrown") {
|
|
const d = m.params.exceptionDetails || {};
|
|
consoleMsgs.push(
|
|
`[exception] ${d.text || ""} ${d.exception && d.exception.description ? d.exception.description : ""}`
|
|
);
|
|
}
|
|
if (m.method === "Log.entryAdded") {
|
|
const e = m.params.entry || {};
|
|
consoleMsgs.push(`[log:${e.level}] ${e.text}`);
|
|
}
|
|
};
|
|
function send(method, params = {}) {
|
|
const myId = ++id;
|
|
return new Promise((res) => {
|
|
pending.set(myId, res);
|
|
sock.send(JSON.stringify({ id: myId, method, params }));
|
|
});
|
|
}
|
|
async function evaluate(expr) {
|
|
const r = await send("Runtime.evaluate", {
|
|
expression: expr,
|
|
awaitPromise: true,
|
|
returnByValue: true,
|
|
});
|
|
if (r.result && r.result.exceptionDetails) {
|
|
return {
|
|
__err: JSON.stringify(r.result.exceptionDetails).slice(0, 500),
|
|
};
|
|
}
|
|
return r.result && r.result.result && r.result.result.value;
|
|
}
|
|
|
|
await send("Runtime.enable");
|
|
await send("Log.enable");
|
|
await send("Page.enable");
|
|
await send("Page.navigate", { url: PAGE });
|
|
await sleep(4000);
|
|
|
|
const report = await evaluate(`(() => {
|
|
const $ = (s) => document.querySelector(s);
|
|
const txt = (s) => { const e=$(s); return e ? (e.textContent||'').trim() : null; };
|
|
const cls = (s) => { const e=$(s); return e ? e.className : null; };
|
|
const nodes = [...document.querySelectorAll('#nodeList .node-item, #nodeList .node, #nodeList [data-node], #nodeList button, #nodeList .node-row')].map(e => ({
|
|
tag: e.tagName,
|
|
cls: e.className,
|
|
text: (e.textContent||'').trim().slice(0,80),
|
|
html: (e.innerHTML||'').slice(0,120)
|
|
}));
|
|
const statusPanel = $('#statusPanel, .status-panel, #statusBody');
|
|
const sb = {};
|
|
for (const id of ['sbLastSend','sbRx','sbTx','sbErr','devStatus','ipLabel','connLed','nodeBadge','netBannerText','refreshRate']) {
|
|
const e = document.getElementById(id);
|
|
sb[id] = e ? {text:(e.textContent||'').trim().slice(0,80), cls:e.className, value:e.value??null, title:e.title||null} : null;
|
|
}
|
|
let dbg = null;
|
|
try {
|
|
const s = window.motorDbg && window.motorDbg.state;
|
|
if (s) {
|
|
dbg = {
|
|
connected: s.connected,
|
|
connecting: s.connecting,
|
|
probing: s.probing,
|
|
tpId: s.tpId,
|
|
ip: s.ip,
|
|
probeErr: s.probeErr,
|
|
probeErrFull: (s.probeErrFull||'').slice(0,200),
|
|
failCount: s.failCount,
|
|
refreshIntervalMs: s.refreshIntervalMs,
|
|
motorsLen: (s.motors||[]).length,
|
|
motorsSample: (s.motors||[]).slice(0,3).map(m=>({ch:m.ch,node:m.node,online:m.online,dlc:m.dlc,dataLen:(m.data||'').length})),
|
|
chOnline: s.chOnline,
|
|
chEnabled: s.chEnabled,
|
|
selectedNode: s.selectedNode,
|
|
decodedSize: s.decoded && s.decoded.size,
|
|
recvTsLen: (s.recvTs||[]).length,
|
|
tickStuck: s.tickStuck,
|
|
txCount: s.txCount,
|
|
transportHas: !!(s.tp),
|
|
transportId: s.tp && s.tp.id,
|
|
lastConnToastAt: s.lastConnToastAt,
|
|
};
|
|
}
|
|
} catch(e) { dbg = {err:String(e)}; }
|
|
|
|
// overlay / empty states visible to user
|
|
const overlays = [...document.querySelectorAll('.offline-overlay,.empty-state,.status-offline,#statusEmpty,.panel-empty')]
|
|
.map(e => ({cls:e.className, text:(e.textContent||'').trim().slice(0,100), hidden:e.hidden, display:getComputedStyle(e).display}));
|
|
|
|
// any toast visible
|
|
const toasts = [...document.querySelectorAll('#toastContainer .toast, .toast')]
|
|
.map(e => ({cls:e.className, text:(e.textContent||'').trim().slice(0,80)}));
|
|
|
|
return {
|
|
title: document.title,
|
|
url: location.href,
|
|
readyState: document.readyState,
|
|
bodyClass: document.body && document.body.className,
|
|
htmlData: document.documentElement.dataset,
|
|
toolbar: {
|
|
brand: txt('.toolbar-brand span'),
|
|
sub: txt('.toolbar-brand .sub'),
|
|
devStatus: txt('#devStatus'),
|
|
connLedCls: cls('#connLed'),
|
|
ipLabel: txt('#ipLabel'),
|
|
transportSelect: document.querySelector('#transportSelect, #linkSelect, select[id*=transport], select[id*=link]')?.value,
|
|
transportOptions: [...document.querySelectorAll('select option')].map(o=>o.value+':'+o.textContent.trim()).slice(0,12),
|
|
},
|
|
sb,
|
|
nodeListCount: nodes.length,
|
|
nodes,
|
|
statusPanelText: statusPanel ? (statusPanel.textContent||'').trim().slice(0,400) : null,
|
|
overlays,
|
|
toasts,
|
|
dbg,
|
|
scripts: [...document.scripts].map(s=>s.src).filter(Boolean),
|
|
};
|
|
})()`);
|
|
|
|
const consoleTail = consoleMsgs.slice(-40);
|
|
const dump = { when: new Date().toISOString(), PAGE, report, consoleTail };
|
|
const outFile = path.join(OUT, "ui_diag.json");
|
|
writeFileSync(outFile, JSON.stringify(dump, null, 2), "utf8");
|
|
console.log("WROTE", outFile);
|
|
console.log(JSON.stringify({ title: report && report.title, toolbar: report && report.toolbar, sb: report && report.sb, nodeListCount: report && report.nodeListCount, nodes: report && report.nodes, overlays: report && report.overlays, toasts: report && report.toasts, dbg: report && report.dbg, scripts: report && report.scripts, consoleTail }, null, 2));
|
|
if (report && report.__err) console.log("EVAL_ERR", report.__err);
|
|
sock.close();
|
|
process.exit(0);
|