; (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()
})()