/*
* ZTE-Script-NG (for ZTE G5TC and later models)
*
* (c) 2025 by Thomas Pöchtrager (t.poechtrager@gmail.com)
* LICENSE: AGPLv3+
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
*/
(function() {
const VERSION = '2025-10-17';
// globals
let currentNetInfo = null;
// --- Logging helpers ---
function scriptMsg(msg) {
console.log(`[script]: ${msg}`);
}
function scriptErrorMsg(msg) {
console.error(`[script error]: ${msg}`);
}
// --- ubus call helper ---
async function callUbus(calls, sessionId=null, omitErrorMsg=false) {
sessionId = sessionId || sessionStorage.getItem("ct");
const callsArray = Array.isArray(calls) ? calls : [calls];
const req = callsArray.map((c, i) => ({
jsonrpc: "2.0",
id: i,
method: "call",
params: [sessionId, c.service, c.method, c.params || {}]
}));
// double t on purpose. Script marker in network log.
const res = await fetch("/ubus/?t=" + Date.now() + "&t=" + Date.now(), {
method: "POST",
headers: {
"Content-Type": "application/json",
"Z-Mode": "1", // This will prevent automatic logout...
},
body: JSON.stringify(req)
}).then(r => r.json());
const byId = new Map(res.map(r => [r.id, r]));
const results = req.map(rq => {
const r = byId.get(rq.id);
if (r?.error) {
if (!omitErrorMsg) {
scriptErrorMsg(
`ubus call error → id=${rq.id}, code=${r.error.code}, message=${r.error.message}, request=${JSON.stringify(rq)}`
);
}
return { success: false, id: rq.id, data: null, accessDenied: r.error.code === -32002 };
}
const code = r?.result?.[0];
if (code === 0) {
return { success: true, id: rq.id, data: r.result[1] };
} else {
if (!omitErrorMsg) {
scriptErrorMsg(
`ubus call failed → id=${rq.id}, code=${code}, request=${JSON.stringify(rq)}`
);
}
return { success: false, id: rq.id, data: null };
}
});
return Array.isArray(calls) ? results : results[0];
}
// --- login ---
async function getLoginPasswordHash(store = true) {
const existing = localStorage.getItem("ScriptPasswordHash");
if (existing) {
return existing;
}
const plain = prompt(
store
? "Enter your router password (SHA256 hash will be stored in localStorage):"
: "Enter your router password:"
);
if (!plain) {
return false;
}
const hash = await sha256Hex(plain);
if (store) {
localStorage.setItem("ScriptPasswordHash", hash);
}
return hash;
}
function clearLoginPasswordHash() {
localStorage.removeItem("ScriptPasswordHash");
}
async function check_login() {
try {
const res = await callUbus({
service: "zwrt_web",
method: "web_developer_login_info",
params: {}
}, null, true);
return res.success && res.data;
} catch {
return false;
}
}
async function login(login_type, password_hash) {
const sessionId =
login_type === "web_login"
? "00000000000000000000000000000000"
: sessionStorage.getItem("ct");
const saltRes = await callUbus(
{
service: "zwrt_web",
method: "web_login_info",
params: {}
},
sessionId
);
const sault = saltRes?.data?.zte_web_sault;
if (!sault) {
throw new Error("Could not retrieve salt");
}
const first = await password_hash;
const finalHash = await sha256Hex(first + sault);
const loginRes = await callUbus(
{
service: "zwrt_web",
method: login_type,
params: { password: finalHash }
},
sessionId
);
const loginResult = loginRes?.data;
if (loginResult?.result === 0) {
if (login_type === "web_login" && loginResult?.ubus_rpc_session) {
sessionStorage.setItem("ct", loginResult.ubus_rpc_session);
}
return true;
}
return false;
}
async function normal_login() {
return login("web_login", getLoginPasswordHash());
}
async function developer_login() {
return login("web_developer_option_login", getLoginPasswordHash(false));
}
// --- helpers ---
// generic retry wrapper for ubus requests
// NOTE: ZTE's web server has a bug and sometimes requests fail with
// Access Denied even though they should succeed.
async function runWithRetry(fn, maxAttempts = 5, logSuccess = false) {
let attempts = 1;
let res;
while (attempts <= maxAttempts) {
res = await fn();
if (!res?.accessDenied) {
break;
}
attempts++;
}
if (logSuccess && attempts > 1 && res?.success) {
scriptMsg(`Request succeeded after ${attempts} attempts.`);
}
return { res, attempts };
}
function showBanner() {
console.log(`
ZTE-Script-NG v${VERSION} loaded.
This script is free to use and licensed under AGPLv3.
Creating it was a lot of work.
If it is helpful to you and you would like to, a tip would be much appreciated.
PayPal: t.poechtrager@gmail.com -- Thank you.
`.replace(/^\s*\n/, "").replace(/^[ \t]+/gm, ""));
}
async function sha256Hex(str) {
const buf = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(str)
);
return Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, "0"))
.join("")
.toUpperCase();
}
function toHex(val, withPrefix = true) {
if (val == null || isNaN(val)) return "-";
const hex = Number(val).toString(16).toUpperCase();
return withPrefix ? "0x" + hex : hex;
}
function formatSeconds(seconds) {
if (!seconds || isNaN(seconds)) return "-";
let s = parseInt(seconds, 10);
const d = Math.floor(s / 86400); s %= 86400;
const h = Math.floor(s / 3600); s %= 3600;
const m = Math.floor(s / 60); s %= 60;
const parts = [];
if (d > 0) parts.push(d + "d");
if (h > 0) parts.push(h + "h");
if (m > 0) parts.push(m + "m");
if (s > 0 || parts.length === 0) parts.push(s + "s");
return parts.join("");
}
function setCurrent4gMask(maskNum) {
const panel = document.getElementById("router-info-panel");
if (panel) {
panel.dataset.current4gMask = maskNum.toString();
}
}
function get4gBandMask(bandNumber) {
return 1n << BigInt(bandNumber - 1);
}
function setCurrent5gBands(bands) {
const panel = document.getElementById("router-info-panel");
if (panel) {
panel.dataset.current5gBands = bands.join(",");
}
}
function is4gBasedNetworkType(type) {
return type === "LTE" || type === "ENDC" || type === "LTE-NSA";
}
function is5gBasedNetworkType(type) {
return type === "SA" || type === "ENDC" || type === "LTE-NSA";
}
// --- Info Window ---
function ShowInfoWindow(title, htmlContent) {
const old = document.getElementById("info-window-overlay");
if (old) old.remove();
const overlay = document.createElement("div");
overlay.id = "info-window-overlay";
overlay.style.cssText = `
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
`;
const box = document.createElement("div");
box.style.cssText = `
background: #fff;
border-radius: 8px;
padding: 20px;
width: 700px;
max-height: 80%;
overflow-y: auto;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: stretch;
`;
const header = document.createElement("div");
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
`;
const h = document.createElement("h3");
h.textContent = title;
h.style.margin = "0";
const closeX = document.createElement("button");
closeX.textContent = "×";
closeX.style.cssText = `
border: none;
background: transparent;
font-size: 26px;
line-height: 1;
cursor: pointer;
color: #000;
`;
closeX.onclick = () => overlay.remove();
header.appendChild(h);
header.appendChild(closeX);
const content = document.createElement("div");
content.innerHTML = htmlContent;
content.style.cssText = `
flex: 1;
`;
const footer = document.createElement("div");
footer.style.cssText = `
text-align: center;
margin-top: 16px;
`;
const closeBtn = document.createElement("button");
closeBtn.textContent = "Close";
closeBtn.style.cssText = `
background:#f9f9f9;
border:1px solid #ccc;
border-radius:4px;
padding:6px 12px;
font-size:14px;
cursor:pointer;
transition:background 0.2s, color 0.2s;
min-width:140px;
color:#000;
`;
closeBtn.onmouseover = () => {
closeBtn.style.background = "#4CAF50";
closeBtn.style.color = "#fff";
};
closeBtn.onmouseout = () => {
closeBtn.style.background = "#f9f9f9";
closeBtn.style.color = "#000";
};
closeBtn.onclick = () => overlay.remove();
footer.appendChild(closeBtn);
box.appendChild(header);
box.appendChild(content);
box.appendChild(footer);
overlay.appendChild(box);
document.body.appendChild(overlay);
// Close when clicking outside the box
overlay.addEventListener("click", (e) => {
if (e.target === overlay) {
overlay.remove();
}
});
}
function buildInfoTableForInfoWindow(title, rows) {
const tableRows = rows.map(([label, value]) => `
| ${label} |
${value ?? "-"} |
`).join("");
return `
`;
}
// --- Info ---
function buildInfoRowsFromValues(values, { excludeKeys = [], excludePrefixes = [] } = {}) {
return Object.entries(values)
.filter(([key, val]) => {
if (excludeKeys.includes(key)) return false;
for (const prefix of excludePrefixes) {
if (key.startsWith(prefix)) return false;
}
if (val === "" || val === null || val === undefined) return false; // skip empty values
return true;
})
.map(([key, val]) => {
const words = key.split("_").map(w => {
if (w.length <= 3) {
return w.toUpperCase();
} else {
return w.charAt(0).toUpperCase() + w.slice(1);
}
});
const label = words.join(" ");
return [label, val];
})
.sort((a, b) => a[0].localeCompare(b[0]));
}
async function showHwAndSwInfo() {
const { res } = await runWithRetry(() =>
callUbus({
service: "uci",
method: "get",
params: {
config: "zwrt_common_info",
section: "common_config"
}
})
);
if (!res || !res.success || !res.data || !res.data.values) {
ShowInfoWindow("HW and SW Info", "No data available
");
return;
}
const rows = buildInfoRowsFromValues(res.data.values, {
excludeKeys: ["imei_sv", "manufacturer"],
excludePrefixes: [".", "sv_"]
});
const html = buildInfoTableForInfoWindow("HW and SW Information", rows);
ShowInfoWindow("HW and SW Info", html);
}
async function showSimInfo() {
const { res } = await runWithRetry(() =>
callUbus({
service: "zwrt_zte_mdm.api",
method: "get_sim_info",
params: {}
})
);
if (!res || !res.success || !res.data) {
ShowInfoWindow("SIM Information", "Failed to retrieve SIM info.
");
return;
}
const values = res.data;
const rows = buildInfoRowsFromValues(values, {
excludePrefixes: ["."],
excludeKeys: ["wlan_mac_address"] // irrelevant hier
});
const html = buildInfoTableForInfoWindow("SIM Information", rows);
ShowInfoWindow("SIM Information", html);
}
async function showWmsInfo() {
const { res } = await runWithRetry(() =>
callUbus({
service: "zwrt_wms",
method: "zwrt_wms_get_wms_capacity",
params: {}
})
);
if (!res || !res.success || !res.data) {
ShowInfoWindow("WMS Information", "Failed to retrieve WMS info.
");
return;
}
const rows = buildInfoRowsFromValues(res.data, {
excludePrefixes: ["."]
});
const html = buildInfoTableForInfoWindow("WMS Information", rows);
ShowInfoWindow("WMS Information", html);
}
async function showWifiInfo() {
const { res } = await runWithRetry(() =>
callUbus([
{ service: "uci", method: "get", params: { config: "wireless", section: "wifi0" } },
{ service: "uci", method: "get", params: { config: "wireless", section: "main_2g" } },
{ service: "uci", method: "get", params: { config: "wireless", section: "wifi1" } }
])
);
if (!Array.isArray(res) || res.length < 2) {
ShowInfoWindow("WiFi Information", "Failed to retrieve WiFi info.
");
return;
}
function filterValues(data) {
if (!data?.data?.values) return [];
// filter out meta keys (those starting with ".")
return Object.entries(data.data.values).filter(([k, _]) => !k.startsWith("."));
}
const wifi0Values = filterValues(res[0]);
const wifi1Values = filterValues(res[1]);
const html = `
${buildInfoTableForInfoWindow("WiFi 2.4 GHz", wifi0Values)}
${buildInfoTableForInfoWindow("WiFi 5 GHz", wifi1Values)}
`;
ShowInfoWindow("WiFi Information", html);
}
async function setWifiParam(paramName, label, validator, formatter = v => v, extraInfoCb = () => "", disclaimer = "") {
const { res } = await runWithRetry(() =>
callUbus([
{ service: "uci", method: "get", params: { config: "wireless", section: "wifi0" } },
{ service: "uci", method: "get", params: { config: "wireless", section: "wifi1" } }
])
);
if (!Array.isArray(res) || res.length < 2) {
alert(`Failed to fetch current WiFi ${label} values.`);
return;
}
const values24 = res[0]?.data?.values || {};
const values5 = res[1]?.data?.values || {};
const current24 = values24[paramName] || "unknown";
const current5 = values5[paramName] || "unknown";
const extra24 = extraInfoCb(values24);
const extra5 = extraInfoCb(values5);
while (true) {
const disclaimerText = disclaimer ? `\n\n${disclaimer}\n` : "";
const input = prompt(
`Enter WiFi ${label} for 2.4 GHz and 5 GHz (e.g. value1,value2)\n` +
`Current:\n 2.4 GHz: ${formatter(current24)}${extra24}\n 5 GHz: ${formatter(current5)}${extra5}` +
disclaimerText,
`${current24},${current5}`
);
if (input === null) return;
const parts = input.split(",").map(p => p.trim());
let val24, val5;
if (parts.length === 1) {
val24 = parts[0];
val5 = val24;
} else if (parts.length === 2) {
val24 = parts[0];
val5 = parts[1];
} else {
alert(`Invalid format. Enter one ${label} or two values separated by comma.`);
continue;
}
if (validator(val24) && validator(val5)) {
const result = await runWithUiFeedback(() =>
callUbus({
service: "zwrt_wlan",
method: "set",
params: {
wifi0: { [paramName]: val24 },
wifi1: { [paramName]: val5 }
}
})
);
if (result?.success) {
scriptMsg(`WiFi ${label} set to ${formatter(val24)} (2.4 GHz) and ${formatter(val5)} (5 GHz)`);
}
return;
}
alert(`Invalid values for ${label}.`);
}
}
// TxPower
async function setWifiTxPower() {
await setWifiParam(
"txpowerpercent",
"Tx Power Percent",
v => {
const n = parseInt(v, 10);
return !isNaN(n) && n >= 1 && n <= 100;
},
v => `${v}%`,
vals => vals.txpower ? ` (${vals.txpower} dBm)` : ""
);
}
// Country
async function setWifiCountry() {
await setWifiParam(
"country",
"Country",
v => v.length === 2,
v => v.toUpperCase(),
() => ""
,
"⚠️ Wrong settings may be illegal in your country or cause connectivity issues."
);
}
async function setWifiMaxClients() {
await setWifiParam(
"maxassoc",
"Max Clients",
v => {
const n = parseInt(v, 10);
return !isNaN(n) && n >= 1 && n <= 512; // limit to a reasonable range
},
v => v // no formatting, keep number as-is
);
}
// --- Network Signal Parsing ---
function convert4gEarfcnToMhz(earfcn) {
const lteBands = [
// [band, f_dl_low, n_offs_dl, n_min_dl, n_max_dl]
[1, 2110, 0, 0, 599],
[3, 1805, 1200, 1200, 1949],
[4, 2110, 1950, 1950, 2399],
[5, 869, 2400, 2400, 2649],
[7, 2620, 2750, 2750, 3449],
[8, 925, 3450, 3450, 3799],
[20, 791, 6150, 6150, 6449],
[28, 758, 9210, 9210, 9659],
[32, 1452, 9920, 9920, 10359], // DL only
[38, 2570, 37750, 37750, 38249],
[40, 2300, 38650, 38650, 39649],
[42, 3400, 41590, 41590, 43589],
[43, 3600, 43590, 43590, 45589],
];
for (const [band, fdl, noffdl, nminDl, nmaxDl] of lteBands) {
if (earfcn >= nminDl && earfcn <= nmaxDl) {
return {
band,
dlMHz: fdl + 0.1 * (earfcn - noffdl)
};
}
}
return null; // unknown EARFCN
}
function convert5gArfcnToMhz(arfcn) {
// Global ARFCN -> MHz mapping according to 3GPP TS 38.104
function arfcnToMHz(N) {
if (N >= 0 && N <= 599999) {
return 0.005 * N;
} else if (N >= 600000 && N <= 2016666) {
return 3000 + 0.015 * (N - 600000);
} else if (N >= 2016667 && N <= 3279165) {
return 24250 + 0.06 * (N - 2016667);
}
return null;
}
// Subset of most relevant NR bands with ARFCN ranges
// [band, n_min, n_max]
const nrBands = [
[1, 422000, 434000],
[3, 361000, 376000],
[5, 173800, 178800],
[7, 524000, 538000],
[8, 185000, 192000],
[28, 151600, 160600],
[40, 460000, 480000],
[41, 499200, 537999],
[75, 286400, 303400], // DL only
[78, 620000, 653333],
[79, 693334, 733333],
// mmWave (FR2)
[257, 2054167, 2104166],
[258, 2016667, 2070833],
[260, 2229167, 2279166],
[261, 2070833, 2084999],
];
for (const [band, nMin, nMax] of nrBands) {
if (arfcn >= nMin && arfcn <= nMax) {
const f = arfcnToMHz(arfcn);
if (f == null) return null;
return { band, dlMHz: +f.toFixed(2) };
}
}
return null; // ARFCN not in supported bands
}
class LteSignal {
constructor({
pci,
earfcn,
bandwidth,
rsrp = null,
rsrq = null,
sinr = null,
rssi = null,
ulConfigured = false,
bandActive = false,
dlFreqMhz = null,
band = null
}) {
this.pci = pci;
this.earfcn = earfcn;
this.bandwidth = bandwidth;
this.rsrp = rsrp;
this.rsrq = rsrq;
this.sinr = sinr;
this.rssi = rssi;
this.ulConfigured = ulConfigured;
this.bandActive = bandActive;
this.dlFreqMhz = dlFreqMhz;
this.band = band;
}
static parse(netInfo) {
const lteca = netInfo?.lteca;
const ltecasig = netInfo?.ltecasig;
if (!lteca) return [];
const caEntries = lteca.split(";").filter(e => e.trim() !== "");
const sigEntries = ltecasig ? ltecasig.split(";").filter(e => e.trim() !== "") : [];
const signals = [];
caEntries.forEach((entry, idx) => {
const parts = entry.split(",").map(p => p.trim());
if (parts.length < 5) return;
const pci = parseInt(parts[0], 10);
const earfcn = parseInt(parts[3], 10);
const bandwidth = parseInt(parts[4], 10);
let rsrp = null, rsrq = null, sinr = null, rssi = null;
let ulConfigured = true, bandActive = true;
if (idx === 0) {
// primary band → values directly from netInfo
rsrp = parseFloat(netInfo.lte_rsrp ?? null);
rsrq = parseFloat(netInfo.lte_rsrq ?? null);
sinr = parseFloat(netInfo.lte_snr ?? null);
rssi = parseFloat(netInfo.lte_rssi ?? null);
} else if (sigEntries[idx - 1]) {
const sigParts = sigEntries[idx - 1].split(",").map(s => s.trim());
if (sigParts.length >= 6) {
rsrp = parseFloat(sigParts[0]);
rsrq = parseFloat(sigParts[1]);
sinr = parseFloat(sigParts[2]);
rssi = parseFloat(sigParts[3]);
ulConfigured = sigParts[4] === "1";
bandActive = sigParts[5] === "2";
}
}
const freq = convert4gEarfcnToMhz(earfcn);
signals.push(new LteSignal({
pci,
earfcn,
bandwidth,
rsrp,
rsrq,
sinr,
rssi,
ulConfigured,
bandActive,
dlFreqMhz: freq ? freq.dlMHz : null,
band: freq ? freq.band : null
}));
});
return signals;
}
static calculateEnodeBAndSectorId(cellId) {
if (!cellId || isNaN(cellId)) return { eNodeB: null, sector: null };
const id = Number(cellId);
return {
eNodeB: id >>> 8,
sector: id & 0xFF
};
}
}
class NrSignal {
constructor({
pci,
arfcn,
bandwidth,
rsrp = null,
rsrq = null,
sinr = null,
rssi = null,
ulConfigured = false,
bandActive = false,
dlFreqMhz = null,
band = null
}) {
this.pci = pci;
this.arfcn = arfcn;
this.bandwidth = bandwidth;
this.rsrp = rsrp;
this.rsrq = rsrq;
this.sinr = sinr;
this.rssi = rssi;
this.ulConfigured = ulConfigured;
this.bandActive = bandActive;
this.dlFreqMhz = dlFreqMhz;
this.band = band;
}
static parse(netInfo) {
const signals = [];
// --- Primary NR cell ---
if (netInfo.nr5g_action_channel) {
const arfcn = parseInt(netInfo.nr5g_action_channel, 10);
const bw = parseInt(netInfo.nr5g_bandwidth, 10);
const pci = parseInt(netInfo.nr5g_pci, 10);
const conv = convert5gArfcnToMhz(arfcn);
let band = null;
if (conv) {
band = conv.band;
} else if (netInfo.nr5g_action_band) {
band = netInfo.nr5g_action_band.replace(/^n/i, ""); // strip leading "n"
}
signals.push(new NrSignal({
pci,
arfcn,
bandwidth: bw,
rsrp: parseFloat(netInfo.nr5g_rsrp ?? null),
rsrq: parseFloat(netInfo.nr5g_rsrq ?? null),
sinr: parseFloat(netInfo.nr5g_snr ?? null),
rssi: parseFloat(netInfo.nr5g_rssi ?? null),
ulConfigured: true, // primary always true
bandActive: true, // primary always active
dlFreqMhz: conv ? conv.dlMHz : null,
band
}));
}
// --- NR CA cells ---
if (netInfo.nrca) {
const caEntries = netInfo.nrca.split(";").filter(e => e.trim() !== "");
caEntries.forEach(entry => {
const parts = entry.split(",").map(p => p.trim());
if (parts.length < 11) return;
const ulConfFlag = parseInt(parts[0], 10);
const pci = parseInt(parts[1], 10);
const activeFlag = parseInt(parts[2], 10);
const band = parts[3] ? "n" + parts[3] : null;
const arfcn = parseInt(parts[4], 10);
const bw = parseInt(parts[5], 10);
const rsrp = parseFloat(parts[7]);
const rsrq = parseFloat(parts[8]);
const sinr = parseFloat(parts[9]);
const rssi = parseFloat(parts[10]);
const conv = convert5gArfcnToMhz(arfcn);
signals.push(new NrSignal({
pci,
arfcn,
bandwidth: bw,
rsrp,
rsrq,
sinr,
rssi,
ulConfigured: ulConfFlag === 1, // 1 = true, 0 = false
bandActive: activeFlag === 2, // 2 = active, 1 = inactive
dlFreqMhz: conv ? conv.dlMHz : null,
band: conv ? conv.band : band
}));
});
}
return signals;
}
static calculateGnodeBAndSectorId(nci) {
if (!nci || isNaN(nci)) return { gNodeB: null, sector: null };
const id = Number(nci);
return {
gNodeB: id >>> 8,
sector: id & 0xFF
};
}
}
class Signal {
constructor() {
this.lteSignal = [];
this.nrSignal = [];
}
static parse(netInfo) {
const signal = new Signal();
// parse LTE
signal.lteSignal = LteSignal.parse(netInfo);
// parse NR
signal.nrSignal = NrSignal.parse(netInfo);
return signal;
}
}
// --- ubus actions ---
async function updateDeviceInfo() {
const res = await callUbus([
{ service: "zte_nwinfo_api", method: "nwinfo_get_netinfo" },
{ service: "zwrt_bsp.thermal", method: "get_cpu_temp" },
{ service: "zwrt_mc.device.manager", method: "get_device_info" },
{ service: "zwrt_router.api", method: "router_get_status" },
{ service: "zwrt_data", method: "get_wwandst", params: { "source_module": "web", "cid": 1, "type": 4 } }
]);
if (Array.isArray(res) && res.length === 5) {
// check if all calls succeeded
const allOk = res.every(r => r?.success);
if (!allOk) {
return;
}
const netRes = res[0];
const tempRes = res[1];
const devRes = res[2];
const wanRes = res[3];
const wanStat = res[4];
const signal = Signal.parse(netRes.data);
// store latest netInfo globally
currentNetInfo = netRes.data;
InfoRenderer.render(
netRes.data,
tempRes.data,
devRes.data,
wanRes.data,
signal,
wanStat.data
);
highlightBearer(netRes.data.net_select);
if (netRes.data.lte_band_lock) {
const maskNum = BigInt(netRes.data.lte_band_lock);
setCurrent4gMask(maskNum);
update4gBandLockHeader(maskNum);
}
if (netRes.data.nr5g_sa_band_lock) {
const activeBands = netRes.data.nr5g_sa_band_lock.split(",").map(b => b.trim());
setCurrent5gBands(activeBands);
update5gBandLockHeader(activeBands);
}
update5gCellLockUi(netRes.data);
update4gCellLockUi(netRes.data);
}
}
async function setBearer(modeId) {
return await callUbus({
service: "zte_nwinfo_api",
method: "nwinfo_set_netselect",
params: { net_select: modeId }
});
}
async function set4gBandLock(maskNum) {
return await callUbus({
service: "zte_nwinfo_api",
method: "nwinfo_set_gwl_bandlock",
params: {
is_gw_band: "0",
gw_band_mask: "0",
is_lte_band: "1",
lte_band_mask: maskNum.toString() // decimal as string
}
});
}
async function set5gBandLock(bands) {
const bandString = bands.join(",");
return await callUbus(
{
service: "zte_nwinfo_api",
method: "nwinfo_set_nrbandlock",
params: {
nr5g_type: "SA", /* This parameter is actually ignored. NSA won't work here. */
nr5g_band: bandString
}
}
);
}
async function lock5gCell(pci, earfcn, band) {
return await callUbus({
service: "zte_nwinfo_api",
method: "nwinfo_lock_nr_cell",
params: {
lock_nr_pci: pci.toString(),
lock_nr_earfcn: earfcn.toString(),
lock_nr_cell_band: band.toString()
}
});
}
async function unlock5gCell() {
return lock5gCell(0, 0, 0);
}
async function lock4gCell(pci, earfcn) {
return await callUbus({
service: "zte_nwinfo_api",
method: "nwinfo_lock_lte_cell",
params: {
lock_lte_pci: pci.toString(),
lock_lte_earfcn: earfcn.toString()
}
});
}
async function unlock4gCell() {
return lock4gCell(0, 0);
}
// --- UI feedback overlay ---
function showUiFeedback(success) {
let overlay = document.getElementById("ui-feedback-overlay");
if (!overlay) {
overlay = document.createElement("div");
overlay.id = "ui-feedback-overlay";
overlay.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 48px;
font-weight: bold;
z-index: 1000;
pointer-events: none;
display: none;
`;
const panelBox = document.getElementById("router-info-box");
panelBox.style.position = "relative";
panelBox.appendChild(overlay);
}
overlay.textContent = success ? "✓" : "✗";
overlay.style.color = success ? "green" : "red";
overlay.style.display = "block";
setTimeout(() => {
overlay.style.display = "none";
}, 1000);
}
// generic retry wrapper for ubus requests
// NOTE: ZTE's web server has a bug and sometimes requests fail even though they should succeed.
async function runWithRetry(fn, maxAttempts = 5, logSuccess = false) {
let attempts = 1;
let res;
while (attempts <= maxAttempts) {
res = await fn();
// detect accessDenied on single object or inside array
const denied = Array.isArray(res)
? res.some(r => r?.accessDenied)
: res?.accessDenied;
if (!denied) {
break;
}
attempts++;
}
// only log success if requested
const allSucceeded = Array.isArray(res)
? res.every(r => r?.success)
: res?.success;
if (logSuccess && attempts > 1 && allSucceeded) {
scriptMsg(`Request succeeded after ${attempts} attempts.`);
}
return { res, attempts };
}
async function runWithUiFeedback(fn) {
try {
const { res } = await runWithRetry(fn, 5, true);
if (!res || res.success === false) {
showUiFeedback(false);
} else {
showUiFeedback(true);
}
updateDeviceInfo();
return res;
} catch (e) {
showUiFeedback(false);
throw e;
}
}
// --- render ---
class InfoRenderer {
static render(netInfo, thermalInfo, deviceInfo, wanInfo, signalInfo, wanStat) {
const netTable = document.getElementById("router-info-table");
const wanTable = document.getElementById("wan-info-table");
const sysTable = document.getElementById("system-info-table");
const trafTable = document.getElementById("traffic-info-table");
if (!netTable || !wanTable || !sysTable || !trafTable) return;
this.renderNetworkInfo(netTable, netInfo, signalInfo, wanStat);
this.renderSignalInfo(netInfo, signalInfo);
this.renderWanInfo(wanTable, wanInfo);
this.renderDeviceInfo(sysTable, thermalInfo, deviceInfo);
this.renderTrafficInfo(trafTable, wanStat);
}
static renderNetworkInfo(table, netInfo, signalInfo, wanStat) {
let bandSummary = "-";
let totalBandwidth = 0;
if (signalInfo) {
const nrBands = [];
const lteBands = [];
if (is5gBasedNetworkType(netInfo?.network_type)) {
signalInfo.nrSignal?.forEach(cell => {
if (cell.band) nrBands.push(`N${cell.band}`);
if (cell.bandwidth) totalBandwidth += cell.bandwidth;
});
}
if (is4gBasedNetworkType(netInfo?.network_type)) {
signalInfo.lteSignal?.forEach(cell => {
if (cell.band) lteBands.push(`B${cell.band}`);
if (cell.bandwidth) totalBandwidth += cell.bandwidth;
});
}
const parts = [];
if (nrBands.length > 0) parts.push(...nrBands);
if (lteBands.length > 0) parts.push(...lteBands);
if (parts.length > 0) {
bandSummary = parts.join(" + ");
}
}
let connType = netInfo.network_type || "-";
if (connType === "SA") {
connType = "5G SA";
} else if (connType === "ENDC") {
connType = "5G NSA";
}
let cellIdDisplay = "-";
let nodeId = null;
let sectorId = null;
if (is4gBasedNetworkType(netInfo.network_type) && netInfo.cell_id) {
const { eNodeB, sector } = LteSignal.calculateEnodeBAndSectorId(netInfo.cell_id);
nodeId = eNodeB;
sectorId = sector;
} else if (netInfo.nr5g_cell_id) {
const { gNodeB, sector } = NrSignal.calculateGnodeBAndSectorId(netInfo.nr5g_cell_id);
nodeId = gNodeB;
sectorId = sector;
}
if (nodeId != null && sectorId != null) {
cellIdDisplay = `${toHex(nodeId, false)}|${toHex(sectorId, false)}`;
}
table.innerHTML = `
| Provider | ${netInfo.network_provider_fullname || "-"} |
| Connection | ${connType} |
| Duration | ${formatSeconds(wanStat.real_time || 0)} |
| Bands | ${bandSummary} |
| BW | ${totalBandwidth > 0 ? totalBandwidth + " MHz" : "-"} |
| Cell ID | ${cellIdDisplay} |
`;
}
static renderSignalInfo(netInfo, signalInfo) {
const sigContainer = document.getElementById("signal-info-container");
if (!sigContainer) return;
sigContainer.innerHTML = "";
function tf(val) {
return val ? "✓" : "✗";
}
// --- NR signals ---
if ((netInfo?.network_type === "SA" || netInfo?.network_type === "ENDC") &&
signalInfo?.nrSignal?.length > 0) {
const grid = document.createElement("div");
grid.className = "signal-grid";
signalInfo.nrSignal.forEach((cell, idx) => {
const box = document.createElement("div");
box.className = "signal-cell";
const bandTitle = cell.band ? `N${cell.band}` : `NR Cell ${idx + 1}`;
box.innerHTML = `
${bandTitle}
| RSRP | ${cell.rsrp ?? "-"} |
| RSRQ | ${cell.rsrq ?? "-"} |
| SINR | ${cell.sinr ?? "-"} |
| RSSI | ${cell.rssi ?? "-"} |
| PCI | ${cell.pci ?? "-"} |
| BW | ${cell.bandwidth ? cell.bandwidth + " MHz" : "-"} |
| ARFCN | ${cell.arfcn ?? "-"} |
| Freq | ${cell.dlFreqMhz ? cell.dlFreqMhz + " MHz" : "-"} |
| UL Configured | ${tf(cell.ulConfigured)} |
| Active | ${tf(cell.bandActive)} |
`;
grid.appendChild(box);
});
sigContainer.appendChild(grid);
}
// --- LTE signals ---
if (is4gBasedNetworkType(netInfo?.network_type) && signalInfo?.lteSignal?.length > 0) {
const grid = document.createElement("div");
grid.className = "signal-grid";
signalInfo.lteSignal.forEach((cell, idx) => {
const box = document.createElement("div");
box.className = "signal-cell";
const bandTitle = cell.band ? `B${cell.band}` : `Cell ${idx + 1}`;
box.innerHTML = `
${bandTitle}
| RSRP | ${cell.rsrp ?? "-"} |
| RSRQ | ${cell.rsrq ?? "-"} |
| SINR | ${cell.sinr ?? "-"} |
| RSSI | ${cell.rssi ?? "-"} |
| PCI | ${cell.pci ?? "-"} |
| BW | ${cell.bandwidth ? cell.bandwidth + " MHz" : "-"} |
| EARFCN | ${cell.earfcn ?? "-"} |
| Freq | ${cell.dlFreqMhz ? cell.dlFreqMhz + " MHz" : "-"} |
| UL Configured | ${tf(cell.ulConfigured)} |
| Active | ${tf(cell.bandActive)} |
`;
grid.appendChild(box);
});
sigContainer.appendChild(grid);
}
}
static renderTrafficInfo(table, wanStat) {
if (!wanStat || !table) return;
// Toggle whether to show error/drop stats (saves space if false)
const show_errors = false;
function fmtBytes(val) {
if (!val || isNaN(val)) return "-";
const units = ["B","KB","MB","GB","TB"];
let v = Number(val);
let i = 0;
while (v >= 1024 && i < units.length - 1) {
v /= 1024;
i++;
}
return v.toFixed(1) + " " + units[i];
}
function fmtSpeed(val) {
if (!val || isNaN(val)) return "-";
const mbit = (Number(val) * 8) / 1e6;
return mbit.toFixed(2) + " Mbit/s";
}
let rows = "";
// --- Current (realtime) stats ---
rows += `| Current |
`;
rows += `| Time | ${formatSeconds(wanStat.real_time)} |
`;
rows += `| DL Speed | ${fmtSpeed(wanStat.real_rx_speed)} |
`;
rows += `| UL Speed | ${fmtSpeed(wanStat.real_tx_speed)} |
`;
rows += `| DL | ${fmtBytes(wanStat.real_rx_bytes)} |
`;
rows += `| UL | ${fmtBytes(wanStat.real_tx_bytes)} |
`;
rows += `| DL Packets | ${wanStat.real_rx_packets ?? "-"} |
`;
rows += `| UL Packets | ${wanStat.real_tx_packets ?? "-"} |
`;
if (show_errors) {
rows += `| Errors (DL/UL) | ${wanStat.real_rx_error_packets}/${wanStat.real_tx_error_packets} |
`;
rows += `| Drops (DL/UL) | ${wanStat.real_rx_drop_packets}/${wanStat.real_tx_drop_packets} |
`;
}
// --- Monthly stats ---
rows += `| Monthly |
`;
rows += `| Time | ${formatSeconds(wanStat.month_time)} |
`;
rows += `| DL | ${fmtBytes(wanStat.month_rx_bytes)} |
`;
rows += `| UL | ${fmtBytes(wanStat.month_tx_bytes)} |
`;
rows += `| DL Packets | ${wanStat.month_rx_packets ?? "-"} |
`;
rows += `| UL Packets | ${wanStat.month_tx_packets ?? "-"} |
`;
if (show_errors) {
rows += `| Errors (DL/UL) | ${wanStat.month_rx_error_packets}/${wanStat.month_tx_error_packets} |
`;
rows += `| Drops (DL/UL) | ${wanStat.month_rx_drop_packets}/${wanStat.month_tx_drop_packets} |
`;
}
// --- Total stats ---
rows += `| Total |
`;
rows += `| Time | ${formatSeconds(wanStat.total_time)} |
`;
rows += `| DL | ${fmtBytes(wanStat.total_rx_bytes)} |
`;
rows += `| UL | ${fmtBytes(wanStat.total_tx_bytes)} |
`;
rows += `| DL Packets | ${wanStat.total_rx_packets ?? "-"} |
`;
rows += `| UL Packets | ${wanStat.total_tx_packets ?? "-"} |
`;
if (show_errors) {
rows += `| Errors (DL/UL) | ${wanStat.total_rx_error_packets}/${wanStat.total_tx_error_packets} |
`;
rows += `| Drops (DL/UL) | ${wanStat.total_rx_drop_packets}/${wanStat.total_tx_drop_packets} |
`;
}
table.innerHTML = rows;
}
static renderWanInfo(table, wanInfo) {
let rows = "";
if (wanInfo) {
rows += `| Mode | ${wanInfo.mwan_wanlan1_link_mode || "-"} |
`;
rows += `| Status | ${wanInfo.mwan_wanlan1_status || "-"} |
`;
rows += `| IPv4 Address | ${wanInfo.mwan_wanlan1_wan_ipaddr || "-"} |
`;
rows += `| Netmask | ${wanInfo.mwan_wanlan1_wan_netmask || "-"} |
`;
rows += `| Gateway | ${wanInfo.mwan_wanlan1_wan_gateway || "-"} |
`;
const dns4 = [wanInfo.mwan_wanlan1_prefer_dns_auto, wanInfo.mwan_wanlan1_standby_dns_auto]
.filter(Boolean).join(", ");
rows += `| DNS | ${dns4 || "-"} |
`;
if (wanInfo.mwan_wanlan1_ipv6_wan_ipaddr && wanInfo.mwan_wanlan1_ipv6_wan_ipaddr !== "0::0") {
rows += `| IPv6 Address | ${wanInfo.mwan_wanlan1_ipv6_wan_ipaddr} |
`;
rows += `| IPv6 Gateway | ${wanInfo.mwan_wanlan1_ipv6_wan_gateway || "-"} |
`;
const dns6 = [wanInfo.mwan_wanlan1_ipv6_prefer_dns_auto, wanInfo.mwan_wanlan1_ipv6_standby_dns_auto]
.filter(Boolean).join(", ");
rows += `| IPv6 DNS | ${dns6 || "-"} |
`;
}
}
table.innerHTML = rows;
}
static renderDeviceInfo(table, thermalInfo, deviceInfo) {
let rows = "";
rows += `| CPU Temp | ${thermalInfo?.cpuss_temp ?? "-"}°C |
`;
if (deviceInfo?.cpuinfo) {
deviceInfo.cpuinfo
.filter(c => c.name !== "all")
.forEach(c => {
const idle = parseFloat(c.idle) || 0;
const load = Math.round(100 - idle);
rows += `| CPU Core ${c.name} | ${load}% |
`;
});
}
if (deviceInfo?.meminfo) {
const total = parseInt(deviceInfo.meminfo.total, 10);
const available = parseInt(deviceInfo.meminfo.avaliable, 10);
const used = total - available;
const percent = total > 0 ? Math.round((used / total) * 100) : 0;
const usedMB = (used / 1024).toFixed(0);
const totalMB = (total / 1024).toFixed(0);
rows += `| Memory | ${usedMB}MB/${totalMB}MB (${percent}%) |
`;
}
if (deviceInfo?.device_uptime) {
rows += `| Uptime | ${formatSeconds(deviceInfo.device_uptime)} |
`;
}
table.innerHTML = rows;
}
}
function highlightBearer(current) {
["Only_5G","LTE_AND_5G","WL_AND_5G","Only_LTE"].forEach(mode => {
const btn = document.getElementById("bearer-" + mode);
if (btn) {
if (mode === current) {
btn.style.background = "#4CAF50";
btn.style.color = "white";
btn.style.fontWeight = "bold";
} else {
btn.style.background = "";
btn.style.color = "";
btn.style.fontWeight = "normal";
}
}
});
}
// --- Update 4G Band Lock Header ---
function update4gBandLockHeader(maskNum) {
const activeBands = [];
// check band bits 1..44
for (let band = 1; band <= 44; band++) {
if ((maskNum & get4gBandMask(band)) !== 0n) {
activeBands.push(band);
}
}
const bandList = activeBands.length > 0 ? activeBands.join(", ") : "auto";
const header = document.getElementById("lte-band-lock-header");
if (header) {
header.textContent = `4G Band Lock: (${bandList})`;
}
}
function update5gBandLockHeader(activeBands) {
// Expecting activeBands as an array of strings, e.g. ["1", "3", "78"]
const bandList = activeBands.length > 0 ? activeBands.join(", ") : "auto";
const header = document.getElementById("nr-band-lock-header");
if (header) {
header.textContent = `5G Band Lock: (${bandList})`;
}
}
// --- Cell lock UI updaters ---
function update5gCellLockUi(info) {
const lockBtn = document.getElementById("btn-lock-5g-cell");
const title = document.getElementById("title-5g-celllock");
if (!lockBtn || !title) return;
lockBtn.dataset.pci = info.nr5g_pci || "";
lockBtn.dataset.earfcn = info.nr5g_action_channel || "";
lockBtn.dataset.band = info.nr5g_action_band
? info.nr5g_action_band.replace("n", "")
: "";
if (info.lock_nr_cell && info.lock_nr_cell.trim() !== "" && info.lock_nr_cell !== "0,0,0") {
title.textContent = `5G Cell Lock (${info.lock_nr_cell})`;
} else {
title.textContent = "5G Cell Lock";
}
}
function update4gCellLockUi(info) {
const lockBtn = document.getElementById("btn-lock-4g-cell");
const title = document.getElementById("title-4g-celllock");
if (!lockBtn || !title) return;
lockBtn.dataset.pci = info.lte_pci || "";
lockBtn.dataset.earfcn = info.lte_action_channel || "";
if (info.lock_lte_cell && info.lock_lte_cell.trim() !== "" && info.lock_lte_cell !== "0,0") {
title.textContent = `4G Cell Lock (${info.lock_lte_cell})`;
} else {
title.textContent = "4G Cell Lock";
}
}
// --- Setup 4G Band Buttons ---
function setup4gBandButtons() {
const SUPPORTED_4G_BANDS = [1, 3, 7, 8, 20, 28, 38, 40, 41, 42, 43];
function buildMask(bands) {
return bands.reduce((mask, b) => mask | get4gBandMask(Number(b)), 0n);
}
function buildFullMask() {
return buildMask(SUPPORTED_4G_BANDS);
}
function setupBandButton(btnId, bands, isAll = false, isManual = false) {
const btn = document.getElementById(btnId);
if (!btn) return;
btn.addEventListener("click", async () => {
let newMask;
if (isAll) {
newMask = buildFullMask();
} else if (isManual) {
while (true) {
const input = prompt("Enter 4G bands (e.g. 1+3+20 or 1,3,20):");
if (input === null) return;
const tokens = input.split(/[\+,]/).map(t => t.trim()).filter(t => t !== "");
if (tokens.length > 0 && tokens.every(t => /^\d+$/.test(t))) {
newMask = buildMask(tokens);
break;
} else {
alert("Invalid input. Please enter band numbers like: 1+3+20");
}
}
} else {
const arr = Array.isArray(bands) ? bands : [bands];
newMask = buildMask(arr);
}
setCurrent4gMask(newMask);
update4gBandLockHeader(newMask);
await runWithUiFeedback(() => set4gBandLock(newMask));
});
}
// Buttons
setupBandButton("lte-band-auto", null, true);
setupBandButton("lte-band-manual", null, false, true);
SUPPORTED_4G_BANDS.forEach(band => {
setupBandButton(`lte-band-b${band}`, band);
});
// Combo buttons
setupBandButton("lte-band-b1b3", ["1", "3"]);
setupBandButton("lte-band-b1b3b7", ["1", "3", "7"]);
}
// --- Setup 5G Band Buttons ---
function setup5gBandButtons() {
const FULL_5G_BANDS = ["1","3","7","8","20","28","38","40","41","75","77","78"];
function setupBandButton(btnId, bands, isAll = false, isManual = false) {
const btn = document.getElementById(btnId);
if (!btn) return;
btn.addEventListener("click", async () => {
let newBands;
if (isAll) {
// All button → all bands
newBands = [...FULL_5G_BANDS];
} else if (isManual) {
while (true) {
const input = prompt("Enter 5G bands (e.g. 1+3+28 or 1,3,28):");
if (input === null) return;
// Split on + or , , trim spaces
const tokens = input.split(/[\+,]/).map(t => t.trim()).filter(t => t !== "");
// Validate: must all be numbers
if (tokens.length > 0 && tokens.every(t => /^\d+$/.test(t))) {
newBands = tokens;
break;
} else {
alert("Invalid input. Please enter band numbers like: 1+3+28");
}
}
} else {
// Specific band(s) → exactly those bands
newBands = Array.isArray(bands) ? bands : [bands];
}
setCurrent5gBands(newBands);
update5gBandLockHeader(newBands);
await runWithUiFeedback(() => set5gBandLock(newBands));
});
}
// All + Manual
setupBandButton("band-auto", null, true);
setupBandButton("band-manual", null, false, true);
// Singles
setupBandButton("band-n1", "1");
setupBandButton("band-n3", "3");
setupBandButton("band-n7", "7");
setupBandButton("band-n28", "28");
setupBandButton("band-n78", "78");
// Combos
setupBandButton("band-n28n75", ["28", "75"])
setupBandButton("band-n78n28n75", ["78", "28", "75"]);
}
function setupInfoCheckboxes() {
const netChk = document.getElementById("chk-network-info");
const wanChk = document.getElementById("chk-wan-info");
const devChk = document.getElementById("chk-device-info");
const sigChk = document.getElementById("chk-signal-info");
const trafChk = document.getElementById("chk-traffic-stats");
const netSection = document.getElementById("network-info-section");
const wanSection = document.getElementById("wan-info-section");
const devSection = document.getElementById("device-info-section");
const sigSection = document.getElementById("signal-info-section");
const trafSection = document.getElementById("traffic-info-section");
// Load states
netChk.checked = localStorage.getItem("ScriptCheckBoxNetworkInfo") !== "false"; // default ON
wanChk.checked = localStorage.getItem("ScriptCheckBoxWanInfo") === "true"; // default OFF
devChk.checked = localStorage.getItem("ScriptCheckBoxDeviceInfo") === "true"; // default OFF
sigChk.checked = localStorage.getItem("ScriptCheckBoxSignalInfo") !== "false"; // default ON
trafChk.checked = localStorage.getItem("ScriptCheckBoxTrafficInfo") === "true"; // default OFF
netSection.style.display = netChk.checked ? "block" : "none";
wanSection.style.display = wanChk.checked ? "block" : "none";
devSection.style.display = devChk.checked ? "block" : "none";
sigSection.style.display = sigChk.checked ? "block" : "none";
trafSection.style.display = trafChk.checked ? "block" : "none";
// Handlers
netChk.addEventListener("change", () => {
localStorage.setItem("ScriptCheckBoxNetworkInfo", netChk.checked);
netSection.style.display = netChk.checked ? "block" : "none";
});
wanChk.addEventListener("change", () => {
localStorage.setItem("ScriptCheckBoxWanInfo", wanChk.checked);
wanSection.style.display = wanChk.checked ? "block" : "none";
});
devChk.addEventListener("change", () => {
localStorage.setItem("ScriptCheckBoxDeviceInfo", devChk.checked);
devSection.style.display = devChk.checked ? "block" : "none";
});
sigChk.addEventListener("change", () => {
localStorage.setItem("ScriptCheckBoxSignalInfo", sigChk.checked);
sigSection.style.display = sigChk.checked ? "block" : "none";
});
trafChk.addEventListener("change", () => {
localStorage.setItem("ScriptCheckBoxTrafficInfo", trafChk.checked);
trafSection.style.display = trafChk.checked ? "block" : "none";
});
}
// --- Global button blur handler ---
function initButtonBlurHandler() {
const panel = document.getElementById("router-info-panel");
if (!panel) return;
panel.addEventListener("click", (e) => {
if (e.target && e.target.tagName === "BUTTON") {
e.target.blur();
}
});
}
// --- UI init ---
function initPanel() {
const old = document.getElementById("router-info-panel");
if (old) old.remove();
const panel = document.createElement("div");
panel.id = "router-info-panel";
panel.style.cssText = "width:100%; margin-bottom:20px;"
panel.innerHTML = `
Network Mode
5G Band Lock
4G Band Lock
5G Cell Lock
4G Cell Lock
General
WiFi
`;
document.body.prepend(panel);
// bearer buttons
document.getElementById("bearer-Only_5G").addEventListener("click", () => runWithUiFeedback(() => setBearer("Only_5G")));
document.getElementById("bearer-LTE_AND_5G").addEventListener("click", () => runWithUiFeedback(() => setBearer("LTE_AND_5G")));
document.getElementById("bearer-WL_AND_5G").addEventListener("click", () => runWithUiFeedback(() => setBearer("WL_AND_5G")));
document.getElementById("bearer-Only_LTE").addEventListener("click", () => runWithUiFeedback(() => setBearer("Only_LTE")));
// setup 4G band buttons
setup4gBandButtons();
// setup 5G band buttons
setup5gBandButtons();
// enable 5g cell lock button
document.getElementById("btn-lock-5g-cell").addEventListener("click", async (e) => {
const pciDefault = e.target.dataset.pci || "";
const earfcnDefault = e.target.dataset.earfcn || "";
const bandDefault = e.target.dataset.band || "";
const defaultText = `${pciDefault},${earfcnDefault},${bandDefault}`;
while (true) {
const input = prompt("Enter PCI,EARFCN,BAND", defaultText);
if (input === null) return;
const parts = input.split(",").map(s => s.trim());
if (parts.length === 3 && parts.every(s => s !== "" && !isNaN(s))) {
const [pci, earfcn, band] = parts;
await runWithUiFeedback(() => lock5gCell(pci, earfcn, band));
return;
}
alert("Invalid format. Please enter numbers as: PCI,EARFCN,BAND");
}
});
// revert 5g cell lock button
document.getElementById("btn-revert-5g-cell").addEventListener("click", async () => {
if (!currentNetInfo?.lock_nr_cell || currentNetInfo.lock_nr_cell === "0,0,0") {
alert("No 5G cell lock is currently active.");
return;
}
const ok = await runWithUiFeedback(() => unlock5gCell());
if (ok) {
alert("Reverted 5G Cell Lock. Toggle net mode or reboot the router to go back to your default Cell now.");
}
});
// enable 4g cell lock button
document.getElementById("btn-lock-4g-cell").addEventListener("click", async (e) => {
const pciDefault = e.target.dataset.pci || "";
const earfcnDefault = e.target.dataset.earfcn || "";
const defaultText = `${pciDefault},${earfcnDefault}`;
while (true) {
const input = prompt("Enter PCI,EARFCN", defaultText);
if (input === null) return;
const parts = input.split(",").map(s => s.trim());
if (parts.length === 2 && parts.every(s => s !== "" && !isNaN(s))) {
const [pci, earfcn] = parts;
await runWithUiFeedback(() => lock4gCell(pci, earfcn));
return;
}
alert("Invalid format. Please enter numbers as: PCI,EARFCN");
}
});
// revert 4g cell lock button
document.getElementById("btn-revert-4g-cell").addEventListener("click", async () => {
if (!currentNetInfo?.lock_lte_cell || currentNetInfo.lock_lte_cell === "0,0") {
alert("No 4G cell lock is currently active.");
return;
}
const ok = await runWithUiFeedback(() => unlock4gCell());
if (ok) {
alert("Reverted 4G Cell Lock. Toggle net mode or reboot the router to go back to your default Cell now.");
}
});
// More button toggle
document.getElementById("btn-more").addEventListener("click", () => {
document.getElementById("btn-more").style.display = "none";
document.getElementById("more-options").style.display = "block";
});
// Action for HW/SW info button
document.getElementById("btn-show-hw-sw").addEventListener("click", async () => {
await showHwAndSwInfo();
});
// Action for WMS info button
document.getElementById("btn-show-sim").addEventListener("click", async () => {
await showSimInfo();
});
// Action for WMS info button
document.getElementById("btn-show-wms").addEventListener("click", async () => {
await showWmsInfo();
});
// Action for WiFi info button
document.getElementById("btn-show-wifi").addEventListener("click", async () => {
await showWifiInfo();
});
// Action for Set WiFi TX Power button
document.getElementById("btn-set-wifi-txpower").addEventListener("click", async () => {
await setWifiTxPower();
});
// Action for Set WiFi Country button
document.getElementById("btn-set-wifi-country").addEventListener("click", async () => {
await setWifiCountry();
});
// Action for Set WiFi max clients button
document.getElementById("btn-set-wifi-max-clients").addEventListener("click", async () => {
await setWifiMaxClients();
});
// auto-refresh
setInterval(updateDeviceInfo, 1000);
// global blur handler for our panel buttons
initButtonBlurHandler();
// info checkboxes
setupInfoCheckboxes();
}
(async () => {
let waitingMsgShown = false;
let interval; // will hold the timer
async function tryInit() {
if (await check_login()) {
scriptMsg("Login successful, initializing panel...");
clearInterval(interval);
initPanel();
return true;
} else {
if (!waitingMsgShown) {
scriptMsg("Waiting for login...");
waitingMsgShown = true;
}
return false;
}
}
// run once immediately
if (!(await tryInit())) {
interval = setInterval(tryInit, 500);
}
showBanner();
})();
})();