From aefa32fc66097100a9fbb66230d70d5c36019cb1 Mon Sep 17 00:00:00 2001 From: crosstyan Date: Tue, 1 Jul 2025 14:20:39 +0800 Subject: [PATCH] init --- fuck_usr.js | 300 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 fuck_usr.js diff --git a/fuck_usr.js b/fuck_usr.js new file mode 100644 index 0000000..99f32d7 --- /dev/null +++ b/fuck_usr.js @@ -0,0 +1,300 @@ +; (async () => { + /** + * @typedef {Object} Gateway + * @property {string} devName - device name + * @property {Element} btn - button element + * @property {string} sn - serial number + */ + + /** + * Centralised timeout/interval constants (milliseconds). + */ + const WAIT = { + POLL_INTERVAL: 100, + PANEL_FADE: 100, + PANEL_VISIBLE_TIMEOUT: 600, + BUTTON_FIND: 5000, + CONFIG_DIALOG_VISIBLE: 8000, + TEMPLATE_LIST_RENDER: 200, + DROPDOWN_CLOSE: 100, + MSGBOX_APPEAR: 2000, + DIALOG_CLOSE: 8000, + DETAIL_PAGE_RENDER: 15000, + TABLE_ROWS_RENDER: 15000, + } + + /** + * Clicks through: 参数信息 → 参数配置 → 参数模板配置 + */ + async function openTemplateConfig() { + // 1. Click the "参数信息" tab + const paramTab = document.getElementById('tab-parameterInfo') + || [...document.querySelectorAll('.el-tabs__item')] + .find(el => el.textContent.trim() === '参数信息') + if (!paramTab) throw new Error('无法找到"参数信息"标签') + if (!paramTab.classList.contains('is-active')) { + // Some implementations bind click on the inner
, + // so try it first, then fall back to the wrapper. + const inner = paramTab.querySelector('#step1, .el-tabs-padding8') + ; (inner ?? paramTab).click() + // wait for its panel to become visible + await new Promise(res => { + const panel = document.getElementById('pane-parameterInfo') + const mo = new MutationObserver(() => { + if (panel.style.display !== 'none') { + mo.disconnect() + // give any fade-in a moment + setTimeout(res, WAIT.PANEL_FADE) + } + }) + mo.observe(panel, { attributes: true, attributeFilter: ['style'] }) + // fallback + setTimeout(() => { mo.disconnect(); res() }, WAIT.PANEL_VISIBLE_TIMEOUT) + }) + } + + // 2. Wait for并点击 "参数配置" 按钮 + const cfgBtn = await waitFor(() => + [...document.querySelectorAll('#pane-parameterInfo button')] + .find(b => b.textContent.trim() === '参数配置'), + WAIT.BUTTON_FIND, + ) + cfgBtn.click() + + // 3. Wait for the configuration dialog to appear + await waitFor(() => { + const dlgWrapper = document.querySelector('.configurationDialog') + return dlgWrapper && getComputedStyle(dlgWrapper).display !== 'none' + }, WAIT.CONFIG_DIALOG_VISIBLE) + + // 4. Click the "参数模板配置" radio + const radio = [...document.querySelectorAll('.configurationDialog .el-radio-button')] + .find(r => r.textContent.trim() === '参数模板配置') + if (!radio) throw new Error('无法找到"参数模板配置"项') + radio.click() + } + + /** + * After 参数模板配置 is active, select a named template, apply it, and confirm the message box. + * @param {string} templateName — e.g. "gw_template_01" + */ + async function applyParameterTemplate(templateName = 'gw_template_01') { + // 1. Open the template-config UI + await openTemplateConfig() // assumes you've already defined openTemplateConfig() + + // 2. Click the 参数配置 dropdown + const selectInput = document.querySelector('.configurationDialog input[placeholder*="参数模板"]') + if (!selectInput) throw new Error('找不到参数模板下拉框') + selectInput.click() + + // 3. Wait a moment for the list to render + await new Promise(r => setTimeout(r, WAIT.TEMPLATE_LIST_RENDER)) + + // 4. Pick the desired template + const option = Array.from(document.querySelectorAll('.el-select-dropdown__item')) + .find(li => li.textContent.trim() === templateName) + if (!option) throw new Error(`模板 "${templateName}" 不存在`) + option.click() + + // 5. Wait for the dropdown to close + await new Promise(r => setTimeout(r, WAIT.DROPDOWN_CLOSE)) + + // 6. Click the "配置" button in the dialog footer + const applyBtn = document.querySelector('.configurationDialog .el-dialog__footer .el-button.el-button--primary') + if (!applyBtn) throw new Error('找不到"配置"按钮') + applyBtn.click() + + // 7. Wait for the confirmation message box to appear + await new Promise(resolve => { + const selector = '.el-message-box__wrapper[aria-label="提示"]' + const check = () => { + const mb = document.querySelector(selector) + if (mb && getComputedStyle(mb).display !== 'none') { + return setTimeout(resolve, WAIT.MSGBOX_APPEAR) + } + return false + } + if (check()) return + const mo = new MutationObserver(() => { + if (check()) { + mo.disconnect() + } + }) + mo.observe(document.body, { childList: true, subtree: true }) + // fallback timeout + setTimeout(() => { mo.disconnect(); resolve() }, WAIT.MSGBOX_APPEAR) + }) + + // 8. Click the "好的" button in the message-box footer + const okBtn = Array.from(document.querySelectorAll('.el-message-box__btns .el-button')) + .find(b => b.textContent.trim().startsWith('好的')) + if (!okBtn) throw new Error('找不到消息框中的"好的"按钮') + okBtn.click() + + // 9. Wait for both message box and configuration dialog to disappear + await waitFor(() => { + const msgWrap = document.querySelector('.el-message-box__wrapper[aria-label="提示"]') + const cfgDlg = document.querySelector('.configurationDialog') + const msgHidden = !msgWrap || getComputedStyle(msgWrap).display === 'none' + const dlgHidden = cfgDlg && getComputedStyle(cfgDlg).display === 'none' + return msgHidden && dlgHidden + }, WAIT.DIALOG_CLOSE) + } + + /** + * @returns {boolean} + */ + function isConsoleLogNative() { + try { + return console.log.toString().includes('native code') + } catch (e) { + return false + } + } + /** + * @description create a clean log function from iframe + * @returns {Function} + */ + function createCleanLog() { + const f = document.createElement('iframe') + f.style.display = 'none' + document.body.appendChild(f) + return f.contentWindow.console.log.bind(f.contentWindow.console) + } + + + /** + * @returns {Gateway[]} + */ + function getGateways() { + /** + * @type {Gateway[]} + */ + const gateways = [] + + const rows = document.querySelectorAll('.el-table__body .el-table__row') + rows.forEach(row => { + const devNameEl = row.querySelector('.DevNameCls') + const snEl = row.querySelectorAll('td')[3] // 4th column is SN + + if (devNameEl && snEl) { + const btn = devNameEl + const devName = devNameEl.textContent.trim() + const sn = snEl.textContent.trim() + gateways.push({ devName, btn, sn }) + } + }) + return gateways + } + + /** + * Wait until a predicate returns a truthy value. + * @template T + * @param {() => T} predicate – polling function + * @param {number} [timeout=5000] – max wait in ms + * @param {number} [interval=100] – polling interval in ms + * @returns {Promise} + */ + function waitFor(predicate, timeout = 5000, interval = WAIT.POLL_INTERVAL) { + const start = Date.now() + return new Promise((resolve, reject) => { + const check = () => { + let res + try { res = predicate() } catch { /* ignore */ } + if (res) return resolve(res) + if (Date.now() - start >= timeout) return reject(new Error('waitFor timeout')) + setTimeout(check, interval) + } + check() + }) + } + + /** + * Waits for a DOM element matching the selector. + * @param {string} selector + * @param {number} [timeout=5000] + * @returns {Promise} + */ + function waitForElement(selector, timeout = 5000) { + return waitFor(() => document.querySelector(selector), timeout) + } + + /** + * Opens gateway details, applies the template, and navigates back. + * @param {string} devName – gateway display name + * @param {string} [templateName='gw_template_01'] + */ + async function processGateway(devName, templateName = 'gw_template_01') { + console.group(`Processing gateway: ${devName}`) + + // Wait for the 参数信息 tab (or any detail-specific element) to appear + await waitFor(() => document.querySelector('#tab-parameterInfo'), WAIT.DETAIL_PAGE_RENDER) + + // 2. Run the existing template-application workflow + await applyParameterTemplate(templateName) + + // 3. Navigate back to the gateway list via breadcrumb + let listLink = [...document.querySelectorAll('.CompBreadcrumb a')] + .find(a => a.textContent.trim() === '网关列表') + if (!listLink) { + // 有时文字在 中 + listLink = [...document.querySelectorAll('.CompBreadcrumb [role="link"]')] + .find(el => el.textContent.trim() === '网关列表') + } + if (!listLink) throw new Error('无法找到"网关列表"面包屑链接') + listLink.click() + + // 4. Wait until the table rows reappear + await waitFor(() => document.querySelectorAll('.el-table__body .el-table__row').length > 0, WAIT.TABLE_ROWS_RENDER) + console.groupEnd() + } + + /** + * Entrypoint – iterates over every gateway and applies the template. + */ + async function init() { + // Expose helpers for manual debugging + Object.assign(window, { + openTemplateConfig, + applyParameterTemplate, + processGateway, + waitFor, + waitForElement, + getGateways, + }) + if (!isConsoleLogNative()) { + const cleanLog = createCleanLog() + console.log = cleanLog + console.info("re-override console.log") + } + const initialGateways = getGateways() + const names = initialGateways.map(g => g.devName) + window.userState = { names } + console.info('Gateways detected:', names) + + for (const devName of names) { + try { + // Re-query each round in case DOM has been re-rendered + const row = [...document.querySelectorAll('.el-table__body .el-table__row')] + .find(r => r.querySelector('.DevNameCls')?.textContent.trim() === devName) + if (!row) { + console.warn(`Row for "${devName}" not found; skipping.`) + continue + } + // Prefer anchor/button inside the DevName cell, else the cell div itself + const btn = row.querySelector('.DevNameCls a, .DevNameCls button') || row.querySelector('.DevNameCls') + if (!btn) { + console.warn(`Gateway button for "${devName}" not found; skipping.`) + continue + } + btn.click() + await processGateway(devName, 'gw_template_01') + } catch (e) { + console.error(`Failed to process gateway "${devName}"`, e) + } + } + + console.info('Automation complete.') + } + await init() +})()