From 7c6d3eabcf1cf110217949be04f86aab41c4fb83 Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Mon, 21 Sep 2026 03:13:16 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E7=95=8C=E9=9D=A2=E8=AF=8A=E6=96=AD?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E5=85=A5=E5=BA=93=EF=BC=9B=E5=BF=BD=E7=95=A5?= =?UTF-8?q?=20=5Fbackup/output=20=E7=8E=B0=E5=9C=BA=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/ui_diag_motor.mjs | 242 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 scripts/ui_diag_motor.mjs diff --git a/scripts/ui_diag_motor.mjs b/scripts/ui_diag_motor.mjs new file mode 100644 index 0000000..41c9805 --- /dev/null +++ b/scripts/ui_diag_motor.mjs @@ -0,0 +1,242 @@ +#!/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);