update 2026-09-26 11:07:27

This commit is contained in:
action committed 2026-09-26 11:07:27 +08:00
1 parent 7b88b273d0
commit 97ae9f68da
4898 files changed
-1712274

No files matched your search

-24
View File
@@ -1,24 +0,0 @@
#
# Copyright (C) 2017-2019 Chen Minqiang <ptpt52@gmail.com>
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Support for wizard
LUCI_DEPENDS:=
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-wizard
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
PKG_LICENSE:=GPLv3
define Package/luci-app-wizard/conffiles
/etc/config/wizard
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
-3
View File
@@ -1,3 +0,0 @@
# luci-app-wizard
修改自 https://github.com/x-wrt/com.x-wrt/tree/master/luci-app-wizard
@@ -1,565 +0,0 @@
'use strict';
'require view';
'require form';
'require uci';
'require fs';
'require ui';
// 辅助工具:统一纯净数组转换
function toArray(val) {
if (Array.isArray(val)) return val.filter(Boolean);
return typeof val === 'string' ? val.trim().split(/\s+/).filter(Boolean) : [];
}
// 辅助工具:提取首个 IP 并去除可能的 CIDR 掩码(容错支持 String、Array 及 null/undefined)
function getFirstIp(val, defaultVal) {
if (Array.isArray(val)) val = val[0];
if (typeof val === 'string') return val.split('/')[0].trim();
return defaultVal || '';
}
// 辅助工具:通用值深度比对(支持标量与数组)
function isEqual(a, b) {
if (Array.isArray(a) || Array.isArray(b)) {
var arrA = toArray(a).sort(), arrB = toArray(b).sort();
return arrA.length === arrB.length && arrA.every(function(v, i) { return v === arrB[i]; });
}
return String(a == null ? '' : a) === String(b == null ? '' : b);
}
// 辅助工具:检查数组是否包含元素(兼容低版本环境)
function has(arr, item) {
return Array.isArray(arr) && arr.indexOf(item) !== -1;
}
// 辅助工具:安全删除 UCI 选项(只有当该选项在 UCI 内存中真实存在时才调用 unset,防止 ubus 报错)
function safeUnset(conf, sid, opt) {
try {
if (uci.get(conf, sid, opt) != null) {
uci.unset(conf, sid, opt);
return true;
}
} catch(e) {}
return false;
}
// 辅助工具:智能判断无线物理网卡的频段 (2.4G / 5G / 6G)
function getRadioBand(devName) {
if (!devName) return '2.4G';
var b = uci.get('wireless', devName, 'band');
if (b === '6g') return '6G';
if (b === '5g') return '5G';
if (b === '2g') return '2.4G';
var ch = parseInt(uci.get('wireless', devName, 'channel'), 10);
if (!isNaN(ch)) {
if (ch >= 1 && ch <= 14) return '2.4G';
if (ch >= 32 && ch <= 196) return '5G';
if (ch > 196) return '6G';
}
var hw = (uci.get('wireless', devName, 'hwmode') || '').toLowerCase();
if (hw.indexOf('a') !== -1) return '5G';
if (hw.indexOf('b') !== -1 || hw.indexOf('g') !== -1) return '2.4G';
var dl = devName.toLowerCase();
if (dl.indexOf('6g') !== -1) return '6G';
if (dl.indexOf('5g') !== -1) return '5G';
return '2.4G';
}
return view.extend({
initialValues: {},
initialShortcuts: [],
originalLanIp: null, // 持久快照,防止被 handleSave 覆盖
hasWireless: false,
hasNginx: false,
map: null,
fields: null,
optMap: null,
// 1. 直连加载系统真实配置包,无需任何中间文件
load: function() {
var pkgs = ['network', 'wireless', 'dhcp', 'uhttpd', 'firewall', 'luci', 'wizard'];
var tasks = pkgs.map(function(p) {
return uci.load(p).catch(function() { return null; });
});
tasks.push(fs.stat('/etc/nginx/uci.conf').then(function() { return true; }).catch(function() { return false; }));
return Promise.all(tasks).then(function(res) {
var wifiSecs = (res && res) ? (uci.sections('wireless', 'wifi-device') || []) : [];
return {
hasWireless: wifiSecs.length > 0,
hasNginx: !!res[7]
};
});
},
// 2. 统一聚合读取系统当前各子模块的实时配置
getSystemConfig: function() {
var lanGw = getFirstIp(uci.get('network', 'lan', 'gateway'), '');
var wanSec = uci.get('network', 'wan');
// 判定 WAN 接口是否不存在或处于停用状态
var isWanDisabled = !wanSec ||
uci.get('network', 'wan', 'auto') === '0' ||
uci.get('network', 'wan', 'disabled') === '1' ||
uci.get('network', 'wan', 'proto') === 'none';
// 核心判定:配置了局域网网关,且 WAN 口被停用或不存在,即确认处于旁路由模式
var isSideRouter = !!lanGw && isWanDisabled;
var ap = null;
if (this.hasWireless) {
var ifaces = uci.sections('wireless', 'wifi-iface') || [];
for (var i = 0; i < ifaces.length; i++) {
if (ifaces[i].mode === 'ap' || !ifaces[i].mode) {
ap = ifaces[i];
break;
}
}
}
// 剥离现有频段后缀,确保输入框展示纯净的基准名称
var rawSsid = (ap && ap.ssid) || (this.hasWireless ? 'Kwrt' : '');
var baseSsid = rawSsid.replace(/_(2\.4G|5G|6G)$/i, '').trim();
return {
wan_proto: isSideRouter ? 'siderouter' : (uci.get('network', 'wan', 'proto') || 'dhcp'),
wan_pppoe_user: uci.get('network', 'wan', 'username') || '',
wan_pppoe_pass: uci.get('network', 'wan', 'password') || '',
lan_ipaddr: getFirstIp(uci.get('network', 'lan', 'ipaddr'), '10.0.0.1'),
lan_gateway: lanGw,
lan_dns: toArray(uci.get('network', 'lan', 'dns')),
dhcp: uci.get('dhcp', 'lan', 'ignore') === '1' ? '0' : '1',
ipv6: uci.get('network', 'wan6', 'auto') === '0' ? '0' : '1',
https: uci.get('wizard', 'default', 'https') || '0',
cookie_p: uci.get('wizard', 'default', 'persistent_cookies') || '1',
landing_page: uci.get('wizard', 'default', 'landing_page') || 'default',
autoupgrade_fm: uci.get('wizard', 'default', 'autoupgrade_fm') || '1',
coremark: uci.get('wizard', 'default', 'coremark') || '0',
wifi_ssid: baseSsid || (this.hasWireless ? 'Kwrt' : ''),
wifi_key: (ap && ap.key) || ''
};
},
render: function(data) {
this.hasWireless = !!data.hasWireless;
this.hasNginx = !!data.hasNginx;
var sys = this.getSystemConfig();
this.initialValues = Object.assign({}, sys);
this.originalLanIp = sys.lan_ipaddr; // 记录不可变初始 IP
// 缓存初始 shortcuts 配置快照,用于保存时比对差异
var shortcuts = uci.sections('wizard', 'shortcuts') || [];
this.initialShortcuts = shortcuts.map(function(sec) {
return {
shortcut: sec.shortcut || '',
to_url: sec.to_url || '',
comments: sec.comments || ''
};
});
var m = new form.Map('wizard', _('Setup Wizard'),
_('Quickly configure common network, wireless, and system settings.'));
var s = m.section(form.NamedSection, 'default', 'wizard');
s.anonymous = true;
s.addremove = false;
s.tab('netsetup', _('Net Settings'));
s.tab('firmware', _('Firmware Settings'));
if (this.hasWireless) s.tab('wifisetup', _('Wireless Settings'));
if (this.hasNginx) {
s.tab('shortcuts', _('Shortcuts'), _('比如设置google.com的快捷方式为字母g,则在此路由器网络的任何浏览器中输入g/即可访问google.com'));
}
// 3. 声明式配置项驱动表
var opt, fields = [
// 网络设置
{ tab: 'netsetup', type: form.ListValue, id: 'wan_proto', title: _('WAN Protocol / Mode'),
choices: { dhcp: _('DHCP Client (Default Router)'), pppoe: _('PPPoE Dial-up'), siderouter: _('Side-Router Mode (Bypass Gateway)') } },
{ tab: 'netsetup', type: form.Value, id: 'wan_pppoe_user', title: _('PPPoE Username'), depends: { wan_proto: 'pppoe' } },
{ tab: 'netsetup', type: form.Value, id: 'wan_pppoe_pass', title: _('PPPoE Password'), depends: { wan_proto: 'pppoe' }, password: true },
{ tab: 'netsetup', type: form.Value, id: 'lan_ipaddr', title: _('LAN IPv4 Address'), datatype: 'ip4addr', placeholder: '10.0.0.1' },
{ tab: 'netsetup', type: form.DynamicList, id: 'lan_dns', title: _('Custom DNS Server(s)'), datatype: 'ipaddr', placeholder: '223.5.5.5', desc: _('Leave empty for ISP DNS.') },
{ tab: 'netsetup', type: form.Value, id: 'lan_gateway', title: _('Gateway Address'), datatype: 'ip4addr', placeholder: '', depends: { wan_proto: 'siderouter' }, desc: _('Primary router IP address used when operating in side-router mode.') },
{ tab: 'netsetup', type: form.Flag, id: 'dhcp', title: _('Enable DHCP Server'), desc: _("If this DHCP is enabled, disable the main router's DHCP. If disabled, manually set client devices' gateway and DNS to this bypass router's IP.") },
{ tab: 'netsetup', type: form.Flag, id: 'ipv6', title: _('IPv6 Support'), desc: _('Enable or disable IPv6 router advertisements and DHCPv6.') },
// 固件与系统设置
{ tab: 'firmware', type: form.Flag, id: 'autoupgrade_fm', title: _('Firmware Upgrade Notice'), desc: _('Check and display notices for newer firmware versions.') },
{ tab: 'firmware', type: form.Flag, id: 'coremark', title: _('Run CoreMark on Boot'), desc: _('Run CPU benchmark asynchronously upon router initialization.') },
{ tab: 'firmware', type: form.Flag, id: 'cookie_p', title: _('Persistent Cookie Session'), desc: _('Maintain persistent login sessions in the web browser.') },
{ tab: 'firmware', type: form.Flag, id: 'https', title: _('Enforce HTTPS Access'), desc: _('Automatically redirect HTTP requests to secure HTTPS.') },
{ tab: 'firmware', type: form.ListValue, id: 'landing_page', title: _('Landing Dashboard Mode'),
choices: { 'default': _('Default'), routerdog: _('RouterDog'), nas: _('NAS'), 'next-nas': _('Next-NAS'), router: _('Router') } }
];
// 无线配置根据设备硬件动态追加
if (this.hasWireless) {
fields.push(
{ tab: 'wifisetup', type: form.Value, id: 'wifi_ssid', title: _('Wireless Network Name (SSID)'), placeholder: 'Kwrt' },
{ tab: 'wifisetup', type: form.Value, id: 'wifi_key', title: _('Wireless Password (Key)'), password: true, placeholder: _('Leave empty for open network or 8+ characters') }
);
}
var optMap = {};
fields.forEach(function(f) {
opt = s.taboption(f.tab, f.type, f.id, f.title, f.desc);
opt.cfgvalue = function() { return sys[f.id]; };
opt.default = sys[f.id];
opt.write = function() {};
opt.remove = function() {};
if (f.password) opt.password = true;
if (f.datatype) opt.datatype = f.datatype;
if (f.placeholder) opt.placeholder = f.placeholder;
if (f.choices) Object.keys(f.choices).forEach(function(k) { opt.value(k, f.choices[k]); });
if (f.depends) Object.keys(f.depends).forEach(function(k) { opt.depends(k, f.depends[k]); });
// 为无线密码增加 8~64 位安全校验,防止 hostapd 崩溃
if (f.id === 'wifi_key') {
opt.validate = function(section_id, value) {
if (!value) return true;
if (value.length < 8 || value.length > 64) {
return _('Wireless key must be between 8 and 64 characters long');
}
return true;
};
}
optMap[f.id] = opt;
});
// Shortcuts GridSection 渲染
if (this.hasNginx) {
var so = s.taboption('shortcuts', form.SectionValue, '_shortcuts', form.GridSection, 'shortcuts', null, _('Shortcuts'));
var ss = so.subsection;
ss.addremove = true;
ss.anonymous = true;
ss.sortable = true;
var o_sc = ss.option(form.Value, 'shortcut', _('Shortcut'));
o_sc.rmempty = false;
o_sc.placeholder = 'g';
o_sc.validate = function(section_id, value) {
if (!value)
return _('Shortcut phrase cannot be empty');
if (!value.match(/^[a-zA-Z0-9_-]+$/))
return _('Only alphanumeric characters, dashes and underscores are allowed');
return true;
};
var o_url = ss.option(form.Value, 'to_url', _('Target URL'));
o_url.rmempty = false;
o_url.placeholder = 'https://example.com';
o_url.validate = function(section_id, value) {
if (value && value.match(/^https?:\/\/.+/i)) {
return true;
}
return _('Please enter a valid URL starting with http:// or https://');
};
var o_comm = ss.option(form.Value, 'comments', _('Comments'));
o_comm.optional = true;
o_comm.placeholder = _('Optional');
}
this.map = m;
this.fields = fields;
this.optMap = optMap;
return m.render();
},
// 4. 重写 View 级别的 handleSave:精准写入发生变化的配置到 UCI 暂存区
handleSave: function(ev) {
var self = this;
if (!this.map) return Promise.resolve(false);
return this.map.parse().then(function() {
var cur = {};
self.fields.forEach(function(f) {
var v = self.optMap[f.id].formvalue('default');
// 对 Flag 进行布尔归一化,彻底消除 null 与 '0' 比对产生的假变动
if (f.type === form.Flag) {
cur[f.id] = (v == '1') ? '1' : '0';
} else {
cur[f.id] = (v != null) ? v : '';
}
});
cur.lan_dns = toArray(cur.lan_dns);
// 精准筛选出发生变化的常规字段
var changed = self.fields.map(function(f) { return f.id; }).filter(function(id) {
return !isEqual(cur[id], self.initialValues[id]);
});
// 检测 shortcuts 是否有增删改
var curShortcuts = (uci.sections('wizard', 'shortcuts') || []).map(function(sec) {
return {
shortcut: sec.shortcut || '',
to_url: sec.to_url || '',
comments: sec.comments || ''
};
});
var shortcutsChanged = JSON.stringify(curShortcuts) !== JSON.stringify(self.initialShortcuts);
// 未做任何改动直接提示并退出
if (changed.length === 0 && !shortcutsChanged) {
ui.addNotification(null, E('p', _('There are no changes to apply')), 'info');
return false;
}
// A. 无线配置直接写回 wireless(全频段适配:2.4G加_2.4G,5G加_5G,6G加_6G)
if (self.hasWireless && (has(changed, 'wifi_ssid') || has(changed, 'wifi_key'))) {
var rawInputSsid = (cur.wifi_ssid || '').trim();
var baseSsid = rawInputSsid.replace(/_(2\.4G|5G|6G)$/i, '').trim();
if (!baseSsid) baseSsid = rawInputSsid;
var ifaces = uci.sections('wireless', 'wifi-iface') || [];
ifaces.forEach(function(ifc) {
if (ifc.mode === 'ap' || !ifc.mode) {
var band = getRadioBand(ifc.device);
var autoSsid = baseSsid ? (baseSsid + '_' + band) : '';
if (has(changed, 'wifi_ssid') && autoSsid) {
uci.set('wireless', ifc['.name'], 'ssid', autoSsid);
}
if (has(changed, 'wifi_key')) {
if (cur.wifi_key) {
uci.set('wireless', ifc['.name'], 'key', cur.wifi_key);
var enc = ifc.encryption || uci.get('wireless', ifc['.name'], 'encryption') || '';
if (enc.indexOf('psk') === -1 && enc.indexOf('sae') === -1) {
uci.set('wireless', ifc['.name'], 'encryption', 'psk2');
}
} else {
safeUnset('wireless', ifc['.name'], 'key');
uci.set('wireless', ifc['.name'], 'encryption', 'none');
}
}
// 解除物理无线与接口禁用状态
if (ifc.device) {
safeUnset('wireless', ifc.device, 'disabled');
}
safeUnset('wireless', ifc['.name'], 'disabled');
}
});
}
// B. WAN 模式与 PPPoE(单网卡判空保护 + LAN 防火墙动态伪装清理)
if (has(changed, 'wan_proto') || has(changed, 'wan_pppoe_user') || has(changed, 'wan_pppoe_pass')) {
var zones = uci.sections('firewall', 'zone') || [];
var lanZone = null;
for (var zi = 0; zi < zones.length; zi++) {
if (zones[zi].name === 'lan') {
lanZone = zones[zi];
break;
}
}
var hasWanSec = !!uci.get('network', 'wan');
if (cur.wan_proto === 'siderouter') {
if (hasWanSec) uci.set('network', 'wan', 'auto', '0');
// 旁路由模式:开启 LAN 区域动态伪装 (masq)
if (lanZone) uci.set('firewall', lanZone['.name'], 'masq', '1');
} else {
if (hasWanSec) {
uci.set('network', 'wan', 'auto', '1');
uci.set('network', 'wan', 'proto', cur.wan_proto);
if (cur.wan_proto === 'pppoe') {
uci.set('network', 'wan', 'username', cur.wan_pppoe_user);
uci.set('network', 'wan', 'password', cur.wan_pppoe_pass);
} else {
safeUnset('network', 'wan', 'username');
safeUnset('network', 'wan', 'password');
}
}
// 切换回主路由模式:恢复清除 LAN 区域的动态伪装 (masq)
if (lanZone) safeUnset('firewall', lanZone['.name'], 'masq');
}
}
// C. LAN IP(兼容 list ipaddr / option ipaddr / CIDR 掩码)
if (has(changed, 'lan_ipaddr') && cur.lan_ipaddr) {
var origIpRaw = uci.get('network', 'lan', 'ipaddr');
var firstIp = Array.isArray(origIpRaw) ? origIpRaw[0] : origIpRaw;
var mask = '';
if (typeof firstIp === 'string') {
var slashIdx = firstIp.indexOf('/');
if (slashIdx !== -1) {
mask = '/' + firstIp.substring(slashIdx + 1).trim();
}
}
var cleanIp = (typeof cur.lan_ipaddr === 'string') ? cur.lan_ipaddr.split('/')[0].trim() : String(cur.lan_ipaddr);
var targetVal = cleanIp + mask;
if (Array.isArray(origIpRaw)) {
var newArr = origIpRaw.slice();
newArr[0] = targetVal;
uci.set('network', 'lan', 'ipaddr', newArr);
} else {
uci.set('network', 'lan', 'ipaddr', targetVal);
if (!mask && !uci.get('network', 'lan', 'netmask')) {
uci.set('network', 'lan', 'netmask', '255.255.255.0');
}
}
}
// D. 自定义 LAN DNS(支持旁路由模式自动回落至网关或公网兜底 DNS)
if (has(changed, 'lan_dns') || has(changed, 'wan_proto') || has(changed, 'lan_gateway')) {
if (cur.wan_proto === 'siderouter') {
var effectiveDns = toArray(cur.lan_dns);
// 若 DNS 列表为空,按优先级进行智能兜底
if (effectiveDns.length === 0) {
var cleanGw = getFirstIp(cur.lan_gateway, '');
if (cleanGw) {
// 优先级 1:回落至主路由网关 IP
effectiveDns.push(cleanGw);
} else {
// 优先级 2:若网关也未填写,使用常用可靠公网 DNS 兜底
effectiveDns.push('223.5.5.5', '119.29.29.29');
}
}
uci.set('network', 'lan', 'dns', effectiveDns);
} else {
// 常规主路由模式:用户未配则清除,让系统通过 WAN 动态获取
if (cur.lan_dns && cur.lan_dns.length > 0) {
uci.set('network', 'lan', 'dns', cur.lan_dns);
} else {
safeUnset('network', 'lan', 'dns');
}
}
}
// E. LAN 网关
if (has(changed, 'lan_gateway') || has(changed, 'wan_proto')) {
if (cur.wan_proto === 'siderouter') {
if (cur.lan_gateway) {
uci.set('network', 'lan', 'gateway', cur.lan_gateway);
} else {
safeUnset('network', 'lan', 'gateway');
}
} else {
safeUnset('network', 'lan', 'gateway');
}
}
// F. DHCP 开关(根据勾选状态精准配置,解除模式死锁冲突)
if (has(changed, 'dhcp') || has(changed, 'wan_proto')) {
if (cur.dhcp === '0') {
uci.set('dhcp', 'lan', 'ignore', '1');
} else {
safeUnset('dhcp', 'lan', 'ignore');
}
}
// G. IPv6
if (has(changed, 'ipv6')) {
var enabled = (cur.ipv6 === '1');
if (uci.get('network', 'wan6')) {
uci.set('network', 'wan6', 'auto', enabled ? '1' : '0');
}
uci.set('dhcp', 'lan', 'ra', enabled ? 'server' : 'disabled');
uci.set('dhcp', 'lan', 'dhcpv6', enabled ? 'server' : 'disabled');
uci.set('dhcp', 'lan', 'ndp', enabled ? 'server' : 'disabled');
}
// H. HTTPS 访问重定向(由 wizard.init 调度底层生效)
if (has(changed, 'https')) {
uci.set('wizard', 'default', 'https', cur.https);
}
// I. 页面与会话设置(持久Cookie双向同步至luci系统底层与向导配置包)
if (has(changed, 'cookie_p')) {
uci.set('wizard', 'default', 'persistent_cookies', cur.cookie_p);
}
if (has(changed, 'landing_page')) {
uci.set('wizard', 'default', 'landing_page', cur.landing_page);
}
// J. 向导专属项(完整保留 autoupgrade_fm 与 coremark)
if (has(changed, 'autoupgrade_fm')) {
uci.set('wizard', 'default', 'autoupgrade_fm', cur.autoupgrade_fm);
}
if (has(changed, 'coremark')) {
uci.set('wizard', 'default', 'coremark', cur.coremark);
}
self.initialValues = Object.assign({}, cur);
self.initialShortcuts = curShortcuts;
return uci.save().then(function() {
return true;
});
});
},
// 5. 重写 View 级别的 handleSaveApply:前置检测 LAN IP 变动,强制非回滚提交并精准弹出倒计时
handleSaveApply: function(ev, mode) {
var self = this;
// 关键修复:在 handleSave 覆盖 initialValues 之前,立即计算并锁定是否修改了 LAN IP
var oldIp = (self.originalLanIp || self.initialValues.lan_ipaddr || '').split('/')[0].trim();
var newIp = (self.optMap['lan_ipaddr'] ? self.optMap['lan_ipaddr'].formvalue('default') : '') || '';
newIp = newIp.split('/')[0].trim();
var ipChanged = !!(newIp && oldIp && (newIp !== oldIp));
return this.handleSave(ev).then(function(hasChanges) {
if (!hasChanges) return false;
if (ipChanged) {
var sec = 15;
var targetUrl = window.location.protocol + '//' + newIp + (window.location.pathname || '/cgi-bin/luci/');
var countSpan = E('strong', {}, String(sec));
// 1. 核心操作:调用 apply_unchecked,进行强制永久提交,彻底绕过 90 秒回滚保护机制!
L.post(L.url('admin', 'uci', 'apply_unchecked')).catch(function() {});
// 2. 立即在前端呼出弹窗遮罩层
ui.showModal(_('LAN IP Address Changed'), [
E('p', {}, [_('LAN IP has been changed to '), E('strong', {}, newIp), '.']),
E('p', {}, [_('Applying changes without rollback. Redirecting to the new address in '), countSpan, _(' seconds...')]),
E('p', { 'class': 'alert-message notice', 'style': 'margin-top: 1em;' },
_('提示:更改网段后,若未能自动加载新页面,请尝试重新插拔网线或断开重连 Wi-Fi,以获取新网段的 IP 地址。')),
E('div', { 'class': 'spinning', 'style': 'margin: 1.5em auto;' }),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() {
window.location.href = targetUrl;
}
}, _('Redirect Now'))
])
]);
// 3. 启动倒计时并自动跳转
var timer = window.setInterval(function() {
sec--;
countSpan.textContent = String(sec);
if (sec <= 0) {
window.clearInterval(timer);
window.location.href = targetUrl;
}
}, 1000);
return true;
} else {
// 未改动 LAN IP 的常规操作,保留原生的回滚自愈保护
return ui.changes.apply(mode == '0');
}
});
},
handleReset: null
});
@@ -1,52 +0,0 @@
module("luci.controller.wizard", package.seeall)
local uci = luci.model.uci.cursor()
local http = require "luci.http"
function index()
entry({"admin", "index"}, call("landing_page"), _("Home") , 0).dependent = false
-- 2. 核心:直接改写 admin 根节点的调度目标
-- 这样当用户访问 /admin、登录后跳转或访问根目录时,无需经过 firstchild(),直接执行 landing_page()
local admin = node("admin")
if admin then
admin.target = call("landing_page")
end
end
local function check_wifi()
local has_wifi = false
pcall(function()
uci:foreach("wireless", "wifi-device", function(s)
has_wifi = true
return false -- 检测到任意 wifi-device 即终止遍历
end)
end)
return has_wifi
end
function landing_page()
local landing_page = uci:get("wizard", "default", "landing_page")
if (luci.sys.call("pgrep routergo >/dev/null") == 0 and landing_page == "routerdog") then
http.redirect(luci.dispatcher.build_url("admin","routerdog"));
elseif luci.sys.call("pgrep quickstart >/dev/null") == 0 then
if landing_page == "nas" then
http.redirect(luci.dispatcher.build_url("admin","istorex","nas"));
elseif landing_page == "next-nas" then
http.redirect(luci.dispatcher.build_url("admin","istorex","next-nas"));
elseif landing_page == "router" then
http.redirect(luci.dispatcher.build_url("admin","istorex","router"));
else
http.redirect(luci.dispatcher.build_url("admin","quickstart"));
end
else
if check_wifi() then
-- 具备无线功能,跳转至常用无线仪表盘界面
http.redirect(luci.dispatcher.build_url("admin","status","dashboard"))
else
-- 无无线设备(纯有线环境),跳转至原生状态概览
http.redirect(luci.dispatcher.build_url("admin", "status", "overview"))
end
end
end
-44
View File
@@ -1,44 +0,0 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "Wizard"
msgstr "Мастер настройки"
msgid "Initial Router Setup"
msgstr "Мастер настройки маршрутизатора"
msgid "Net Settings"
msgstr "Сетевые настройки"
msgid "Wireless Settings"
msgstr "Настройки беспроводной связи"
msgid "If you are using this router for the first time, please configure it here."
msgstr "Если вы используете этот маршрутизатор в первый раз, пожалуйста, просто настройте его здесь."
msgid "Three different ways to access the Internet, please choose according to your own situation."
msgstr "Существует два различных способа серфинга в Интернете, пожалуйста, выбирайте в соответствии с вашей собственной ситуацией."
msgid "Set the router's wireless name and password. For more advanced settings, please go to the Network-Wireless page."
msgstr "Задайте имя беспроводной сети и пароль маршрутизатора. для получения более подробных настроек, пожалуйста, перейдите на страницу Cеть-Беспроводная связь."
msgid "Siderouter"
msgstr "Вторичный роутер"
msgid "Enable IPv6"
msgstr "Включить IPv6"
msgid "Enable/Disable IPv6"
msgstr "Включение/Отключение IPv6"
msgid "Firmware Settings"
msgstr "Настройки встроенного ПО"
msgid "Packages Auto Upgrade"
msgstr "Автоматическое обновление пакета"
msgid "Firmware Upgrade Notice"
msgstr "Напоминание об обновлении прошивки"
msgid "Persistent cookies"
msgstr "Постоянный вход в систему"
-181
View File
@@ -1,181 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: luci-app-wizard\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-20 20:00+0800\n"
"PO-Revision-Date: 2026-09-20 20:00+0800\n"
"Last-Translator: Eric\n"
"Language-Team: Chinese (Simplified) <zh_Hans>\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Home"
msgstr "首页"
msgid "Setup Wizard"
msgstr "快速设置向导"
msgid "Quickly configure common network, wireless, and system settings."
msgstr "快速配置常用网络、无线与系统运行设置。"
msgid "Net Settings"
msgstr "网络设置"
msgid "Firmware Settings"
msgstr "固件设置"
msgid "Wireless Settings"
msgstr "无线设置"
msgid "Shortcuts"
msgstr "域名快捷跳转"
msgid "比如设置google.com的快捷方式为字母g,则在此路由器网络的任何浏览器中输入g/即可访问google.com"
msgstr "比如设置 google.com 的快捷方式为字母 g,则在此路由器网络的任何浏览器中输入 g/ 即可访问 google.com"
msgid "WAN Protocol / Mode"
msgstr "WAN 协议 / 工作模式"
msgid "DHCP Client (Default Router)"
msgstr "DHCP 客户端(标准主路由模式)"
msgid "PPPoE Dial-up"
msgstr "PPPoE 拨号"
msgid "Side-Router Mode (Bypass Gateway)"
msgstr "旁路由模式(网关旁路)"
msgid "PPPoE Username"
msgstr "PPPoE 用户名"
msgid "PPPoE Password"
msgstr "PPPoE 密码"
msgid "LAN IPv4 Address"
msgstr "LAN IPv4 地址"
msgid "Custom DNS Server(s)"
msgstr "自定义 DNS 服务器"
msgid "Gateway Address"
msgstr "网关地址"
msgid "Primary router IP address used when operating in side-router mode."
msgstr "在旁路由模式下工作时填写的上级主路由 IP 地址。"
msgid "Enable DHCP Server"
msgstr "启用 DHCP 服务器"
msgid "If this DHCP is enabled, disable the main router's DHCP. If disabled, manually set client devices' gateway and DNS to this bypass router's IP."
msgstr "开启此DHCP则需要关闭主路由DHCP, 关闭此DHCP则需要手动将上网设备的网关和DNS改为此旁路由的IP。"
msgid "Leave empty for ISP DNS."
msgstr "留空则使用 ISP 运营商DNS"
msgid "IPv6 Support"
msgstr "IPv6 支持"
msgid "Enable or disable IPv6 router advertisements and DHCPv6."
msgstr "开启或关闭 IPv6 路由通告 (RA) 与 DHCPv6 服务。"
msgid "Firmware Upgrade Notice"
msgstr "固件更新检测提示"
msgid "Check and display notices for newer firmware versions."
msgstr "开机或访问时检测并提示可用的新版本固件。"
msgid "Run CoreMark on Boot"
msgstr "开机自动运行 CoreMark 跑分"
msgid "Run CPU benchmark asynchronously upon router initialization."
msgstr "路由器启动完成后在后台异步运行 CPU 性能基准测试。"
msgid "Persistent Cookie Session"
msgstr "保持登录会话 (Persistent Cookie)"
msgid "Maintain persistent login sessions in the web browser."
msgstr "关闭浏览器后仍保持管理后台登录状态。"
msgid "Enforce HTTPS Access"
msgstr "强制 HTTPS 访问"
msgid "Automatically redirect HTTP requests to secure HTTPS."
msgstr "自动将普通 HTTP 请求重定向到加密的 HTTPS 连接。"
msgid "Landing Dashboard Mode"
msgstr "管理首页展示模式"
msgid "Default"
msgstr "默认"
msgid "RouterDog"
msgstr "路由狗"
msgid "NAS"
msgstr "NAS"
msgid "Next-NAS"
msgstr "Next-NAS"
msgid "Router"
msgstr "Router"
msgid "Wireless Network Name (SSID)"
msgstr "无线网络名称 (SSID)"
msgid "无线名称基准前缀,系统将自动识别并为 2.4G 追加 _2.4G、5G 追加 _5G、6G 追加 _6G。"
msgstr "无线名称基准前缀,系统将自动识别并为 2.4G 追加 _2.4G、5G 追加 _5G、6G 追加 _6G。"
msgid "Wireless Password (Key)"
msgstr "无线密码"
msgid "Leave empty for open network or 8+ characters"
msgstr "留空表示无密码开放网络,或输入至少 8 位密码"
msgid "Wireless key must be between 8 and 64 characters long"
msgstr "无线密码长度必须在 8 到 64 个字符之间"
msgid "Shortcut"
msgstr "快捷短语"
msgid "Shortcut phrase cannot be empty"
msgstr "快捷短语不能为空"
msgid "Only alphanumeric characters, dashes and underscores are allowed"
msgstr "仅允许输入字母、数字、破折号 (-) 和下划线 (_)"
msgid "Target URL"
msgstr "目标网址 (URL)"
msgid "Please enter a valid URL starting with http:// or https://"
msgstr "请输入以 http:// 或 https:// 开头的有效网址"
msgid "Comments"
msgstr "备注说明"
msgid "Optional"
msgstr "可选"
msgid "There are no changes to apply"
msgstr "没有需要应用的更改"
msgid "LAN IP Address Changed"
msgstr "LAN 口 IP 地址已变更"
msgid "LAN IP has been changed to "
msgstr "LAN 口 IP 地址已更改为 "
msgid "Applying changes without rollback. Redirecting to the new address in "
msgstr "正在永久应用更改(已禁用回滚保护)。将在 "
msgid " seconds..."
msgstr " 秒后自动跳转至新地址..."
msgid "提示:更改网段后,若未能自动加载新页面,请尝试重新插拔网线或断开重连 Wi-Fi,以获取新网段的 IP 地址。"
msgstr "提示:更改网段后,若未能自动加载新页面,请尝试重新插拔网线或断开重连 Wi-Fi,以获取新网段的 IP 地址。"
msgid "Redirect Now"
msgstr "立即跳转"
-1
View File
@@ -1 +0,0 @@
config wizard 'default'
-150
View File
@@ -1,150 +0,0 @@
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
# 清理历史已生成的快捷方式(防止改名或删除后遗留失效条目)
clean_shortcuts() {
local sec
# 清理 nginx 中由 wizard 生成的 _sc_* server 节
for sec in $(uci -q show nginx 2>/dev/null | sed -n 's/^nginx\.\(_sc_[^=.]*\)=server$/\1/p'); do
uci -q delete nginx."$sec"
done
# 清理 dhcp (dnsmasq) 中由 wizard 生成的 _sc_* domain 节
for sec in $(uci -q show dhcp 2>/dev/null | sed -n 's/^dhcp\.\(_sc_[^=.]*\)=domain$/\1/p'); do
uci -q delete dhcp."$sec"
done
}
# 处理单条 shortcut 节
process_shortcut() {
local shortcut to_url comments lanaddr
local cfg="$1"
# 获取当前 LAN 口 IP,支持 DSA 列表与 CIDR 掩码剔除
lanaddr="$(uci -q get network.lan.ipaddr | tr ' ' '\n' | head -n1)"
lanaddr="${lanaddr%%/*}"
[ -z "$lanaddr" ] && lanaddr="10.0.0.1"
config_get shortcut "$cfg" shortcut
config_get to_url "$cfg" to_url
config_get comments "$cfg" comments
# 校验必要参数
if [ -z "$shortcut" ] || [ -z "$to_url" ]; then
return 0
fi
# 1. 配置 Nginx 302 重定向 (仅在存在 /etc/nginx/uci.conf 时配置,仅监听80,禁止未配证书监听443)
if [ -f /etc/nginx/uci.conf ]; then
uci -q set nginx._sc_$shortcut=server
uci -q set nginx._sc_$shortcut.server_name="$shortcut"
uci -q add_list nginx._sc_$shortcut.listen="80"
uci -q set nginx._sc_$shortcut.return="302 $to_url"
# 2. 配置 Dnsmasq (DHCP) 本地域名劫持
uci -q set dhcp._sc_$shortcut=domain
uci -q set dhcp._sc_$shortcut.name="$shortcut"
uci -q set dhcp._sc_$shortcut.ip="$lanaddr"
[ -n "$comments" ] && uci -q set dhcp._sc_$shortcut.comments="$comments"
fi
}
# 配置 HTTPS 访问重定向 (适配 Nginx / Lighttpd / uHTTPd)
sethttps() {
local cfg="${1:-default}"
local https
config_get https "$cfg" https 0
if [ -x /etc/init.d/nginx ] && [ -f /etc/nginx/uci.conf ]; then
if [ "x${https}" = "x1" ]; then
if [ -z "$(uci -q get nginx._redirect2ssl)" ]; then
uci -q set nginx._redirect2ssl=server
uci -q set nginx._redirect2ssl.server_name='_redirect2ssl'
uci -q set nginx._redirect2ssl.return='302 https://$host$request_uri'
uci -q set nginx._redirect2ssl.access_log='off; # logd openwrt'
fi
uci -q del nginx.default_server.listen
uci -q add_list nginx.default_server.listen='80'
uci -q add_list nginx.default_server.listen='[::]:80'
uci -q del nginx._redirect2ssl.listen
uci -q add_list nginx._redirect2ssl.listen='80 default_server'
uci -q add_list nginx._redirect2ssl.listen='[::]:80 default_server'
else
uci -q delete nginx._redirect2ssl
uci -q del nginx.default_server.listen
uci -q add_list nginx.default_server.listen='80 default_server'
uci -q add_list nginx.default_server.listen='[::]:80 default_server'
fi
uci -q commit nginx
/etc/init.d/nginx reload >/dev/null 2>&1
elif [ -x /etc/init.d/lighttpd ] && [ -f /etc/lighttpd/conf.d/82-https.conf ]; then
if [ "x${https}" = "x1" ]; then
sed -i "s/^[ \t]*#*[ \t]*url\.redirect[ \t]*=.*/\turl.redirect =/" /etc/lighttpd/conf.d/82-https.conf 2>/dev/null
else
sed -i "s/^[ \t]*url\.redirect[ \t]*=.*/\t# url.redirect =/" /etc/lighttpd/conf.d/82-https.conf 2>/dev/null
fi
/etc/init.d/lighttpd reload >/dev/null 2>&1
elif [ -x /etc/init.d/uhttpd ]; then
if [ "x${https}" = "x1" ]; then
uci -q set uhttpd.main.redirect_https='1'
else
uci -q set uhttpd.main.redirect_https='0'
fi
uci -q commit uhttpd
/etc/init.d/uhttpd reload >/dev/null 2>&1
fi
}
# 提交配置并平滑重载底层服务
reload_services() {
uci -q commit dhcp
if [ -x /etc/init.d/dnsmasq ]; then
/etc/init.d/dnsmasq reload >/dev/null 2>&1
fi
if [ -f /etc/nginx/uci.conf ] && [ -x /etc/init.d/nginx ]; then
uci -q commit nginx
/etc/init.d/nginx reload >/dev/null 2>&1
fi
}
start_service() {
config_load wizard
# 1. 开机 CoreMark 跑分处理 (后台异步执行,杜绝阻塞系统启动与前端保存卡死)
local coremark
config_get coremark default coremark 0
if [ "$coremark" = "1" ] && [ -x /bin/coremark ] && [ -x /sbin/coremark ] && [ ! -f /etc/bench.log ] && [ ! -f /var/lock/opkg.lock ]; then
(sleep 5;/sbin/coremark >/dev/null 2>&1) &
fi
# 2. 持久 Cookie 会话配置同步到 LuCI 底层系统
local cookie_p
config_get cookie_p default persistent_cookies 1
if [ -n "$cookie_p" ]; then
[ -z "$(uci -q get luci.sgi)" ] && uci -q set luci.sgi=sgi
uci -q set luci.sgi.persistent_cookies="$cookie_p"
uci -q commit luci
fi
# 3. 清理并重新配置所有 Shortcuts
clean_shortcuts
config_foreach process_shortcut shortcuts
# 4. 设置 HTTPS 重定向
sethttps default
# 5. 提交并重载生效服务
reload_services
}
stop_service() {
clean_shortcuts
reload_services
}
service_triggers() {
procd_add_reload_trigger "wizard"
}
@@ -1,14 +0,0 @@
{
"admin/system/wizard": {
"title": "Setup Wizard",
"order": 10,
"action": {
"type": "view",
"path": "wizard/initsetup"
},
"depends": {
"acl": [ "luci-app-wizard" ],
"uci": { "wizard": true }
}
}
}
@@ -1,30 +0,0 @@
{
"luci-app-wizard": {
"description": "Grant UCI and filesystem access for LuCI Setup Wizard",
"read": {
"uci": [
"network",
"wireless",
"dhcp",
"uhttpd",
"firewall",
"luci",
"wizard"
],
"file": {
"/etc/nginx/uci.conf": [ "stat" ]
}
},
"write": {
"uci": [
"network",
"wireless",
"dhcp",
"uhttpd",
"firewall",
"luci",
"wizard"
]
}
}
}