update 2026-05-10 09:48:30

This commit is contained in:
action committed 2026-05-10 09:48:30 +08:00
1 parent 95ac96413e
commit 0b362674f1
6244 files changed
+2992248

No files matched your search

@@ -0,0 +1,152 @@
'use strict';
'require baseclass';
'require uci';
'require fs';
'require rpc';
'require request';
const callRCList = rpc.declare({
object: 'rc',
method: 'list',
params: ['name'],
expect: { '': {} }
});
const callRCInit = rpc.declare({
object: 'rc',
method: 'init',
params: ['name', 'action'],
expect: { '': {} }
});
const callMomoGetPaths = rpc.declare({
object: 'luci.momo',
method: 'get_paths',
expect: { '': {} }
});
const callMomoVersion = rpc.declare({
object: 'luci.momo',
method: 'version',
expect: { '': {} }
});
const callMomoProfile = rpc.declare({
object: 'luci.momo',
method: 'profile',
params: ['defaults'],
expect: { '': {} }
});
const callMomoUpdateSubscription = rpc.declare({
object: 'luci.momo',
method: 'update_subscription',
params: ['section_id'],
expect: { '': {} }
});
const callMomoAPI = rpc.declare({
object: 'luci.momo',
method: 'api',
params: ['method', 'path', 'query', 'body'],
expect: { '': {} }
});
const callMomoGetIdentifiers = rpc.declare({
object: 'luci.momo',
method: 'get_identifiers',
expect: { '': {} }
});
const callMomoDebug = rpc.declare({
object: 'luci.momo',
method: 'debug',
expect: { '': {} }
});
return baseclass.extend({
getPaths: async function () {
return callMomoGetPaths();
},
status: async function () {
return (await callRCList('momo'))?.momo?.running;
},
reload: function () {
return callRCInit('momo', 'reload');
},
restart: function () {
return callRCInit('momo', 'restart');
},
version: function () {
return callMomoVersion();
},
profile: function (defaults) {
return callMomoProfile(defaults);
},
updateSubscription: function (section_id) {
return callMomoUpdateSubscription(section_id);
},
updateDashboard: function () {
return callMomoAPI('POST', '/upgrade/ui');
},
openDashboard: async function () {
const profile = await callMomoProfile({ 'experimental': { 'clash_api': { 'external_controller': null, 'secret': null } } });
const apiListen = profile?.['experimental']?.['clash_api']?.['external_controller'];
const apiSecret = profile?.['experimental']?.['clash_api']?.['secret'] ?? '';
if (!apiListen) {
return Promise.reject('Clash API has not been configured');
}
const apiPort = apiListen.substring(apiListen.lastIndexOf(':') + 1);
const params = {
host: window.location.hostname,
hostname: window.location.hostname,
port: apiPort,
secret: apiSecret
};
const query = new URLSearchParams(params).toString();
const url = `http://${window.location.hostname}:${apiPort}/ui/?${query}`;
setTimeout(function () { window.open(url, '_blank') }, 0);
return Promise.resolve();
},
getIdentifiers: function () {
return callMomoGetIdentifiers();
},
listProfiles: async function () {
const paths = await this.getPaths();
return L.resolveDefault(fs.list(paths.profiles_dir), []);
},
getAppLog: async function () {
const paths = await this.getPaths();
return L.resolveDefault(fs.read_direct(paths.app_log_path));
},
getCoreLog: async function () {
const paths = await this.getPaths();
return L.resolveDefault(fs.read_direct(paths.core_log_path));
},
clearAppLog: async function () {
const paths = await this.getPaths();
return fs.write(paths.app_log_path);
},
clearCoreLog: async function () {
const paths = await this.getPaths();
return fs.write(paths.core_log_path);
},
debug: function () {
return callMomoDebug();
},
})
@@ -0,0 +1,169 @@
'use strict';
'require form';
'require view';
'require uci';
'require poll';
'require tools.momo as momo';
function renderStatus(running) {
return updateStatus(E('input', { id: 'core_status', style: 'border: unset; font-style: italic; font-weight: bold;', readonly: '' }), running);
}
function updateStatus(element, running) {
if (element) {
element.style.color = running ? 'green' : 'red';
element.value = running ? _('Running') : _('Not Running');
}
return element;
}
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
momo.version(),
momo.status(),
momo.listProfiles()
]);
},
render: function (data) {
const subscriptions = uci.sections('momo', 'subscription');
const appVersion = data[1].app ?? '';
const coreVersion = data[1].core ?? '';
const running = data[2];
const profiles = data[3];
let m, s, o;
m = new form.Map('momo', _('Momo'), `${_('Transparent Proxy with sing-box on OpenWrt.')} <a href="https://github.com/nikkinikki-org/OpenWrt-momo/wiki" target="_blank">${_('How To Use')}</a>`);
s = m.section(form.TableSection, 'placeholder', _('Status'));
s.anonymous = true;
o = s.option(form.Value, '_app_version', _('App Version'));
o.readonly = true;
o.load = function () {
return appVersion;
};
o.write = function () { };
o = s.option(form.Value, '_core_version', _('Core Version'));
o.readonly = true;
o.load = function () {
return coreVersion;
};
o.write = function () { };
o = s.option(form.DummyValue, '_core_status', _('Core Status'));
o.cfgvalue = function () {
return renderStatus(running);
};
poll.add(function () {
return L.resolveDefault(momo.status()).then(function (running) {
updateStatus(document.getElementById('core_status'), running);
});
});
o = s.option(form.Button, 'reload');
o.inputstyle = 'action';
o.inputtitle = _('Reload Service');
o.onclick = function () {
return momo.reload();
};
o = s.option(form.Button, 'restart');
o.inputstyle = 'negative';
o.inputtitle = _('Restart Service');
o.onclick = function () {
return momo.restart();
};
o = s.option(form.Button, 'update_dashboard');
o.inputstyle = 'positive';
o.inputtitle = _('Update Dashboard');
o.onclick = function () {
return momo.updateDashboard();
};
o = s.option(form.Button, 'open_dashboard');
o.inputtitle = _('Open Dashboard');
o.onclick = function () {
return momo.openDashboard();
};
s = m.section(form.NamedSection, 'config', 'config', _('App Config'));
o = s.option(form.Flag, 'enabled', _('Enable'));
o.rmempty = false;
o = s.option(form.ListValue, 'profile', _('Choose Profile'));
o.optional = true;
for (const profile of profiles) {
o.value('file:' + profile.name, _('File:') + profile.name);
};
for (const subscription of subscriptions) {
o.value('subscription:' + subscription['.name'], _('Subscription:') + subscription.name);
};
o = s.option(form.Value, 'start_delay', _('Start Delay'));
o.datatype = 'uinteger';
o.placeholder = _('Start Immidiately');
o = s.option(form.Flag, 'scheduled_restart', _('Scheduled Restart'));
o.rmempty = false;
o = s.option(form.Value, 'scheduled_restart_cron', _('Scheduled Restart Cron'));
o.retain = true;
o.rmempty = false;
o.depends('scheduled_restart', '1');
o = s.option(form.Flag, 'test_profile', _('Test Profile'));
o.rmempty = false;
o = s.option(form.Flag, 'core_only', _('Core Only'));
o.rmempty = false;
s = m.section(form.NamedSection, 'procd', 'procd', _('procd Config'));
s.tab('general', _('General Config'));
o = s.taboption('general', form.Flag, 'fast_reload', _('Fast Reload'));
o.rmempty = false;
s.tab('rlimit', _('RLIMIT Config'));
o = s.taboption('rlimit', form.Value, 'rlimit_address_space_soft', _('Address Space Size Soft Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_address_space_hard', _('Address Space Size Hard Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_data_soft', _('Heap Size Soft Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_data_hard', _('Heap Size Hard Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_stack_soft', _('Stack Size Soft Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_stack_hard', _('Stack Size Hard Limit'));
o.datatype = 'uinteger';
o.placeholder = _('Unlimited');
o = s.taboption('rlimit', form.Value, 'rlimit_nofile_soft', _('Number of Open Files Soft Limit'));
o.datatype = 'uinteger';
o = s.taboption('rlimit', form.Value, 'rlimit_nofile_hard', _('Number of Open Files Hard Limit'));
o.datatype = 'uinteger';
return m.render();
}
});
@@ -0,0 +1,69 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require tools.momo as momo'
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
momo.getPaths(),
momo.listProfiles(),
]);
},
render: function (data) {
const subscriptions = uci.sections('momo', 'subscription');
const paths = data[1];
const profiles = data[2];
let m, s, o;
m = new form.Map('momo');
s = m.section(form.NamedSection, 'placeholder', 'placeholder', _('Editor'));
o = s.option(form.ListValue, '_file', _('Choose File'));
o.optional = true;
for (const profile of profiles) {
o.value(paths.profiles_dir + '/' + profile.name, _('File:') + profile.name);
};
for (const subscription of subscriptions) {
o.value(paths.subscriptions_dir + '/' + subscription['.name'] + '.json', _('Subscription:') + subscription.name);
};
o.value(paths.run_profile_path, _('Profile for Startup'));
o.write = function (section_id, formvalue) {
return true;
};
o.onchange = function (event, section_id, value) {
return L.resolveDefault(fs.read_direct(value), '').then(function (content) {
m.lookupOption('_file_content', section_id)[0].getUIElement(section_id).setValue(content);
});
};
o = s.option(form.TextValue, '_file_content',);
o.rows = 25;
o.wrap = false;
o.write = function (section_id, formvalue) {
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
return fs.write(path, formvalue);
};
o.remove = function (section_id) {
const path = m.lookupOption('_file', section_id)[0].formvalue(section_id);
return fs.write(path);
};
return m.render();
},
handleSaveApply: function (ev, mode) {
return this.handleSave(ev).finally(function () {
return mode === '0' ? momo.reload() : momo.restart();
});
},
handleReset: null
});
@@ -0,0 +1,147 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require poll';
'require tools.momo as momo';
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
momo.getPaths(),
momo.getAppLog(),
momo.getCoreLog()
]);
},
render: function (data) {
const paths = data[1];
const appLog = data[2];
const coreLog = data[3];
let m, s, o;
m = new form.Map('momo');
s = m.section(form.NamedSection, 'log', 'log', _('Log'));
s.tab('log_config', _('Log Config'));
o = s.taboption('log_config', form.Flag, 'scheduled_clear', _('Scheduled Clear'));
o.rmempty = false;
o = s.taboption('log_config', form.Value, 'scheduled_clear_cron', _('Scheduled Clear Cron'));
o.retain = true;
o.rmempty = false;
o.depends('scheduled_clear', '1');
o = s.taboption('log_config', form.Value, 'scheduled_clear_size_limit', _('Scheduled Clear Size Limit'));
o.retain = true;
o.rmempty = false;
o.datatype = 'uinteger';
o.depends('scheduled_clear', '1');
o = s.taboption('log_config', form.ListValue, 'scheduled_clear_size_limit_unit', _('Scheduled Clear Size Limit Unit'));
o.retain = true;
o.rmempty = false;
o.depends('scheduled_clear', '1');
o.value('KB', 'KB');
o.value('MB', 'MB');
o.value('GB', 'GB');
s.tab('app_log', _('App Log'));
o = s.taboption('app_log', form.Button, 'clear_app_log');
o.inputstyle = 'negative';
o.inputtitle = _('Clear Log');
o.onclick = function (_, section_id) {
m.lookupOption('_app_log', section_id)[0].getUIElement(section_id).setValue('');
return momo.clearAppLog();
};
o = s.taboption('app_log', form.TextValue, '_app_log');
o.rows = 25;
o.wrap = false;
o.load = function (section_id) {
return appLog;
};
o.write = function (section_id, formvalue) {
return true;
};
poll.add(L.bind(function () {
const option = this;
return L.resolveDefault(momo.getAppLog()).then(function (log) {
option.getUIElement('log').setValue(log);
});
}, o));
o = s.taboption('app_log', form.Button, 'scroll_app_log_to_bottom');
o.inputtitle = _('Scroll To Bottom');
o.onclick = function (_, section_id) {
const element = m.lookupOption('_app_log', section_id)[0].getUIElement(section_id).node.firstChild;
element.scrollTop = element.scrollHeight;
};
s.tab('core_log', _('Core Log'));
o = s.taboption('core_log', form.Button, 'clear_core_log');
o.inputstyle = 'negative';
o.inputtitle = _('Clear Log');
o.onclick = function (_, section_id) {
m.lookupOption('_core_log', section_id)[0].getUIElement(section_id).setValue('');
return momo.clearCoreLog();
};
o = s.taboption('core_log', form.TextValue, '_core_log');
o.rows = 25;
o.wrap = false;
o.load = function (section_id) {
return coreLog;
};
o.write = function (section_id, formvalue) {
return true;
};
poll.add(L.bind(function () {
const option = this;
return L.resolveDefault(momo.getCoreLog()).then(function (log) {
option.getUIElement('log').setValue(log);
});
}, o));
o = s.taboption('core_log', form.Button, 'scroll_core_log_to_bottom');
o.inputtitle = _('Scroll To Bottom');
o.onclick = function (_, section_id) {
const element = m.lookupOption('_core_log', section_id)[0].getUIElement(section_id).node.firstChild;
element.scrollTop = element.scrollHeight;
};
s.tab('debug_log', _('Debug Log'));
o = s.taboption('debug_log', form.Button, '_generate_download_debug_log');
o.inputstyle = 'negative';
o.inputtitle = _('Generate & Download');
o.onclick = function () {
return momo.debug().then(function () {
fs.read_direct(paths.debug_log_path, 'blob').then(function (data) {
// create url
const url = window.URL.createObjectURL(data, { type: 'text/markdown' });
// create link
const link = document.createElement('a');
link.href = url;
link.download = 'debug.log';
// append to body
document.body.appendChild(link);
// download
link.click();
// remove from body
document.body.removeChild(link);
// revoke url
window.URL.revokeObjectURL(url);
});
});
};
return m.render();
}
});
@@ -0,0 +1,154 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require network';
'require poll';
'require tools.widgets as widgets';
'require tools.momo as momo';
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
]);
},
render: function (data) {
let m, s, o;
m = new form.Map('momo');
s = m.section(form.NamedSection, 'mixin', 'mixin', _('Mixin Option'));
s.tab('log', _('Log Config'));
o = s.taboption('log', form.ListValue, 'log_disabled', _('Log Disabled'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('0', _('Disable'));
o.value('1', _('Enable'));
o = s.taboption('log', form.ListValue, 'log_level', _('Log Level'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('panic');
o.value('fatal');
o.value('error');
o.value('warn');
o.value('info');
o.value('debug');
o.value('trace');
o = s.taboption('log', form.ListValue, 'log_timestamp', _('Log Timestamp'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('0', _('Disable'));
o.value('1', _('Enable'));
o = s.taboption('log', form.Value, 'log_output', _('Log Output'));
o.placeholder = _('Unmodified');
s.tab('dns', _('DNS Config'));
o = s.taboption('dns', form.ListValue, 'dns_strategy', _('DNS Strategy'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('prefer_ipv4', _('Prefer IPv4'));
o.value('prefer_ipv6', _('Prefer IPv6'));
o.value('ipv4_only', _('IPv4 Only'));
o.value('ipv6_only', _('IPv6 Only'));
o = s.taboption('dns', form.ListValue, 'dns_disable_cache', _('DNS Disable Cache'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('0', _('Disable'));
o.value('1', _('Enable'));
o = s.taboption('dns', form.ListValue, 'dns_disable_expire', _('DNS Disable Expire'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('0', _('Disable'));
o.value('1', _('Enable'));
o = s.taboption('dns', form.ListValue, 'dns_independent_cache', _('DNS Independent Cache'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('0', _('Disable'));
o.value('1', _('Enable'));
o = s.taboption('dns', form.Value, 'dns_cache_capacity', _('DNS Cache Capacity'));
o.datatype = 'uinteger';
o.placeholder = _('Unmodified');
o = s.taboption('dns', form.ListValue, 'dns_reverse_mapping', _('DNS Reverse Mapping'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('0', _('Disable'));
o.value('1', _('Enable'));
s.tab('ntp', _('NTP Config'));
o = s.taboption('ntp', form.ListValue, 'ntp_enabled', _('NTP Enabled'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('0', _('Disable'));
o.value('1', _('Enable'));
o = s.taboption('ntp', form.Value, 'ntp_server', _('NTP Server'));
o.placeholder = _('Unmodified');
o = s.taboption('ntp', form.Value, 'ntp_server_port', _('NTP Server Port'));
o.datatype = 'port';
o.placeholder = _('Unmodified');
o = s.taboption('ntp', form.Value, 'ntp_interval', _('NTP Interval'));
o.placeholder = _('Unmodified');
s.tab('cache', _('Cache Config'));
o = s.taboption('cache', form.ListValue, 'cache_enabled', _('Cache Enabled'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('0', _('Disable'));
o.value('1', _('Enable'));
o = s.taboption('cache', form.Value, 'cache_path', _('Cache Path'));
o.placeholder = _('Unmodified');
o = s.taboption('cache', form.ListValue, 'cache_store_fakeip', _('Cache Store FakeIP'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('0', _('Disable'));
o.value('1', _('Enable'));
o = s.taboption('cache', form.ListValue, 'cache_store_rdrc', _('Cache Store RDRC'));
o.optional = true;
o.placeholder = _('Unmodified');
o.value('0', _('Disable'));
o.value('1', _('Enable'));
s.tab('external_control', _('External Control Config'));
o = s.taboption('external_control', form.Value, 'external_control_ui_path', _('UI Path'));
o.placeholder = _('Unmodified');
o = s.taboption('external_control', form.Value, 'external_control_ui_download_url', _('UI Download Url'));
o.placeholder = _('Unmodified');
o.value('https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip', 'Zashboard (CDN Fonts)');
o.value('https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip', 'Zashboard');
o.value('https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip', 'MetaCubeXD');
o.value('https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD');
o.value('https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord');
o = s.taboption('external_control', form.Value, 'external_control_api_listen', _('API Listen'));
o.datatype = 'ipaddrport(1)';
o.placeholder = _('Unmodified');
o = s.taboption('external_control', form.Value, 'external_control_api_secret', _('API Secret'));
o.password = true;
o.placeholder = _('Unmodified');
return m.render();
}
});
@@ -0,0 +1,90 @@
'use strict';
'require form';
'require view';
'require uci';
'require tools.momo as momo';
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
momo.getPaths(),
]);
},
render: function (data) {
const paths = data[1];
let m, s, o, so;
m = new form.Map('momo');
s = m.section(form.NamedSection, 'config', 'config', _('Profile'));
o = s.option(form.FileUpload, '_upload_profile', _('Upload Profile'));
o.browser = true;
o.enable_download = true;
o.root_directory = paths.profiles_dir;
o.write = function (section_id, formvalue) {
return true;
};
s = m.section(form.GridSection, 'subscription', _('Subscription'));
s.addremove = true;
s.anonymous = true;
s.sortable = true;
s.modaltitle = _('Edit Subscription');
o = s.option(form.Value, 'name', _('Subscription Name'));
o.rmempty = false;
o = s.option(form.Value, 'used', _('Used'));
o.modalonly = false;
o.optional = true;
o.readonly = true;
o = s.option(form.Value, 'total', _('Total'));
o.modalonly = false;
o.optional = true;
o.readonly = true;
o = s.option(form.Value, 'expire', _('Expire At'));
o.modalonly = false;
o.optional = true;
o.readonly = true;
o = s.option(form.Value, 'update', _('Update At'));
o.modalonly = false;
o.optional = true;
o.readonly = true;
o = s.option(form.Button, 'update_subscription');
o.editable = true;
o.inputstyle = 'positive';
o.inputtitle = _('Update');
o.modalonly = false;
o.onclick = function (_, section_id) {
return momo.updateSubscription(section_id);
};
o = s.option(form.Value, 'info_url', _('Subscription Info Url'));
o.modalonly = true;
o = s.option(form.Value, 'url', _('Subscription Url'));
o.modalonly = true;
o.rmempty = false;
o = s.option(form.Value, 'user_agent', _('User Agent'));
o.default = 'sing-box';
o.modalonly = true;
o.rmempty = false;
o.value('sing-box');
o = s.option(form.ListValue, 'prefer', _('Prefer'));
o.default = 'remote';
o.modalonly = true;
o.value('remote', _('Remote'));
o.value('local', _('Local'));
return m.render();
}
});
@@ -0,0 +1,212 @@
'use strict';
'require form';
'require view';
'require uci';
'require network';
'require tools.widgets as widgets';
'require tools.momo as momo';
return view.extend({
load: function () {
return Promise.all([
uci.load('momo'),
network.getHostHints(),
network.getNetworks(),
momo.getIdentifiers(),
]);
},
render: function (data) {
const hosts = data[1].hosts;
const networks = data[2];
const users = data[3]?.users ?? [];
const groups = data[3]?.groups ?? [];
const cgroups = data[3]?.cgroups ?? [];
let m, s, o, so;
m = new form.Map('momo');
s = m.section(form.NamedSection, 'proxy', 'proxy', _('Proxy Config'));
s.tab('proxy', _('Proxy Config'));
o = s.taboption('proxy', form.Flag, 'enabled', _('Enable'));
o.rmempty = false;
o = s.taboption('proxy', form.Flag, 'ipv4_dns_hijack', _('IPv4 DNS Hijack'));
o.rmempty = false;
o = s.taboption('proxy', form.Flag, 'ipv6_dns_hijack', _('IPv6 DNS Hijack'));
o.rmempty = false;
o = s.taboption('proxy', form.Flag, 'ipv4_proxy', _('IPv4 Proxy'));
o.rmempty = false;
o = s.taboption('proxy', form.Flag, 'ipv6_proxy', _('IPv6 Proxy'));
o.rmempty = false;
o = s.taboption('proxy', form.Flag, 'fake_ip_ping_hijack', _('Fake-IP Ping Hijack'));
o.rmempty = false;
o = s.taboption('proxy', form.ListValue, 'tcp_mode', _('TCP Mode'));
o.optional = true;
o.placeholder = _('Disable');
o.value('redirect', _('Redirect Mode'));
o.value('tproxy', _('TPROXY Mode'));
o.value('tun', _('TUN Mode'));
o = s.taboption('proxy', form.ListValue, 'udp_mode', _('UDP Mode'));
o.optional = true;
o.placeholder = _('Disable');
o.value('tproxy', _('TPROXY Mode'));
o.value('tun', _('TUN Mode'));
s.tab('router', _('Router Proxy'));
o = s.taboption('router', form.Flag, 'router_proxy', _('Enable'));
o.rmempty = false;
o = s.taboption('router', form.SectionValue, '_router_access_control', form.TableSection, 'router_access_control', _('Access Control'));
o.retain = true;
o.depends('router_proxy', '1');
o.subsection.addremove = true;
o.subsection.anonymous = true;
o.subsection.sortable = true;
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
so.default = '1';
so.rmempty = false;
so = o.subsection.option(form.DynamicList, 'user', _('User'));
for (const user of users) {
so.value(user);
};
so = o.subsection.option(form.DynamicList, 'group', _('Group'));
for (const group of groups) {
so.value(group);
};
so = o.subsection.option(form.DynamicList, 'cgroup', _('CGroup'));
for (const cgroup of cgroups) {
so.value(cgroup);
};
so = o.subsection.option(form.Flag, 'dns', _('DNS'));
so.rmempty = false;
so = o.subsection.option(form.Flag, 'proxy', _('Proxy'));
so.rmempty = false;
s.tab('lan', _('LAN Proxy'));
o = s.taboption('lan', form.Flag, 'lan_proxy', _('Enable'));
o.rmempty = false;
o = s.taboption('lan', form.DynamicList, 'lan_inbound_interface', _('Inbound Interface'));
o.retain = true;
o.rmempty = false;
o.depends('lan_proxy', '1');
for (const network of networks) {
if (network.getName() === 'loopback') {
continue;
}
o.value(network.getName());
}
o = s.taboption('lan', form.SectionValue, '_lan_access_control', form.TableSection, 'lan_access_control', _('Access Control'));
o.retain = true;
o.depends('lan_proxy', '1');
o.subsection.addremove = true;
o.subsection.anonymous = true;
o.subsection.sortable = true;
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
so.default = '1';
so.rmempty = false;
so = o.subsection.option(form.DynamicList, 'ip', 'IP');
so.datatype = 'ip4addr';
for (const mac in hosts) {
const host = hosts[mac];
for (const ip of host.ipaddrs) {
const hint = host.name ?? mac;
so.value(ip, hint ? '%s (%s)'.format(ip, hint) : ip);
};
};
so = o.subsection.option(form.DynamicList, 'ip6', 'IP6');
so.datatype = 'ip6addr';
for (const mac in hosts) {
const host = hosts[mac];
for (const ip of host.ip6addrs) {
const hint = host.name ?? mac;
so.value(ip, hint ? '%s (%s)'.format(ip, hint) : ip);
};
};
so = o.subsection.option(form.DynamicList, 'mac', 'MAC');
so.datatype = 'macaddr';
for (const mac in hosts) {
const host = hosts[mac];
const hint = host.name ?? host.ipaddrs[0];
so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
};
so = o.subsection.option(form.Flag, 'dns', _('DNS'));
so.rmempty = false;
so = o.subsection.option(form.Flag, 'proxy', _('Proxy'));
so.rmempty = false;
s.tab('bypass', _('Bypass'));
o = s.taboption('bypass', form.Flag, 'bypass_china_mainland_ip', _('Bypass China Mainland IP'));
o.rmempty = false;
o = s.taboption('bypass', form.Flag, 'bypass_china_mainland_ip6', _('Bypass China Mainland IP6'));
o.rmempty = false;
o = s.taboption('bypass', form.Value, 'proxy_tcp_dport', _('Destination TCP Port to Proxy'));
o.rmempty = false;
o.value('0-65535', _('All Port'));
o.value('21 22 80 110 143 194 443 465 853 993 995 8080 8443', _('Commonly Used Port'));
o = s.taboption('bypass', form.Value, 'proxy_udp_dport', _('Destination UDP Port to Proxy'));
o.rmempty = false;
o.value('0-65535', _('All Port'));
o.value('123 443 8443', _('Commonly Used Port'));
o = s.taboption('bypass', form.DynamicList, 'bypass_dscp', _('Bypass DSCP'));
o.datatype = 'range(0, 63)';
o = s.taboption('bypass', form.DynamicList, 'bypass_fwmark', _('Bypass FWMark'));
s.tab('misc', _('Misc'));
o = s.taboption('misc', form.DynamicList, 'reserved_ip', _('Reserved IP'));
o.datatype = 'ip4addr';
o = s.taboption('misc', form.DynamicList, 'reserved_ip6', _('Reserved IP6'));
o.datatype = 'ip6addr';
o = s.taboption('misc', form.Value, 'tun_timeout', _('TUN Timeout'));
o.datatype = 'uinteger';
o.rmempty = false;
o = s.taboption('misc', form.Value, 'tun_interval', _('TUN Interval'));
o.datatype = 'uinteger';
o.rmempty = false;
return m.render();
}
});