🐶 Sync 2025-11-02 14:26:26
This commit is contained in:
10
luci-app-nikki/Makefile
Normal file
10
luci-app-nikki/Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.24.3
|
||||
|
||||
LUCI_TITLE:=LuCI Support for nikki
|
||||
LUCI_DEPENDS:=+luci-base +nikki
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
178
luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js
Normal file
178
luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js
Normal file
@@ -0,0 +1,178 @@
|
||||
'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 callNikkiVersion = rpc.declare({
|
||||
object: 'luci.nikki',
|
||||
method: 'version',
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const callNikkiProfile = rpc.declare({
|
||||
object: 'luci.nikki',
|
||||
method: 'profile',
|
||||
params: [ 'defaults' ],
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const callNikkiUpdateSubscription = rpc.declare({
|
||||
object: 'luci.nikki',
|
||||
method: 'update_subscription',
|
||||
params: ['section_id'],
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const callNikkiAPI = rpc.declare({
|
||||
object: 'luci.nikki',
|
||||
method: 'api',
|
||||
params: ['method', 'path', 'query', 'body'],
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const callNikkiGetIdentifiers = rpc.declare({
|
||||
object: 'luci.nikki',
|
||||
method: 'get_identifiers',
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const callNikkiDebug = rpc.declare({
|
||||
object: 'luci.nikki',
|
||||
method: 'debug',
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const homeDir = '/etc/nikki';
|
||||
const profilesDir = `${homeDir}/profiles`;
|
||||
const subscriptionsDir = `${homeDir}/subscriptions`;
|
||||
const mixinFilePath = `${homeDir}/mixin.yaml`;
|
||||
const runDir = `${homeDir}/run`;
|
||||
const runProfilePath = `${runDir}/config.yaml`;
|
||||
const providersDir = `${runDir}/providers`;
|
||||
const ruleProvidersDir = `${providersDir}/rule`;
|
||||
const proxyProvidersDir = `${providersDir}/proxy`;
|
||||
const logDir = `/var/log/nikki`;
|
||||
const appLogPath = `${logDir}/app.log`;
|
||||
const coreLogPath = `${logDir}/core.log`;
|
||||
const debugLogPath = `${logDir}/debug.log`;
|
||||
const nftDir = `${homeDir}/nftables`;
|
||||
|
||||
return baseclass.extend({
|
||||
homeDir: homeDir,
|
||||
profilesDir: profilesDir,
|
||||
subscriptionsDir: subscriptionsDir,
|
||||
mixinFilePath: mixinFilePath,
|
||||
runDir: runDir,
|
||||
runProfilePath: runProfilePath,
|
||||
ruleProvidersDir: ruleProvidersDir,
|
||||
proxyProvidersDir: proxyProvidersDir,
|
||||
appLogPath: appLogPath,
|
||||
coreLogPath: coreLogPath,
|
||||
debugLogPath: debugLogPath,
|
||||
|
||||
status: async function () {
|
||||
return (await callRCList('nikki'))?.nikki?.running;
|
||||
},
|
||||
|
||||
reload: function () {
|
||||
return callRCInit('nikki', 'reload');
|
||||
},
|
||||
|
||||
restart: function () {
|
||||
return callRCInit('nikki', 'restart');
|
||||
},
|
||||
|
||||
version: function () {
|
||||
return callNikkiVersion();
|
||||
},
|
||||
|
||||
profile: function (defaults) {
|
||||
return callNikkiProfile(defaults);
|
||||
},
|
||||
|
||||
updateSubscription: function (section_id) {
|
||||
return callNikkiUpdateSubscription(section_id);
|
||||
},
|
||||
|
||||
updateDashboard: function () {
|
||||
return callNikkiAPI('POST', '/upgrade/ui');
|
||||
},
|
||||
|
||||
openDashboard: async function () {
|
||||
const profile = await callNikkiProfile({ 'external-ui-name': null, 'external-controller': null, 'secret': null });
|
||||
const uiName = profile['external-ui-name'];
|
||||
const apiListen = profile['external-controller'];
|
||||
const apiSecret = profile['secret'] ?? '';
|
||||
if (!apiListen) {
|
||||
return Promise.reject('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();
|
||||
let url;
|
||||
if (uiName) {
|
||||
url = `http://${window.location.hostname}:${apiPort}/ui/${uiName}/?${query}`;
|
||||
} else {
|
||||
url = `http://${window.location.hostname}:${apiPort}/ui/?${query}`;
|
||||
}
|
||||
setTimeout(function () { window.open(url, '_blank') }, 0);
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
getIdentifiers: function () {
|
||||
return callNikkiGetIdentifiers();
|
||||
},
|
||||
|
||||
listProfiles: function () {
|
||||
return L.resolveDefault(fs.list(this.profilesDir), []);
|
||||
},
|
||||
|
||||
listRuleProviders: function () {
|
||||
return L.resolveDefault(fs.list(this.ruleProvidersDir), []);
|
||||
},
|
||||
|
||||
listProxyProviders: function () {
|
||||
return L.resolveDefault(fs.list(this.proxyProvidersDir), []);
|
||||
},
|
||||
|
||||
getAppLog: function () {
|
||||
return L.resolveDefault(fs.read_direct(this.appLogPath));
|
||||
},
|
||||
|
||||
getCoreLog: function () {
|
||||
return L.resolveDefault(fs.read_direct(this.coreLogPath));
|
||||
},
|
||||
|
||||
clearAppLog: function () {
|
||||
return fs.write(this.appLogPath);
|
||||
},
|
||||
|
||||
clearCoreLog: function () {
|
||||
return fs.write(this.coreLogPath);
|
||||
},
|
||||
|
||||
debug: function () {
|
||||
return callNikkiDebug();
|
||||
},
|
||||
})
|
||||
193
luci-app-nikki/htdocs/luci-static/resources/view/nikki/app.js
Normal file
193
luci-app-nikki/htdocs/luci-static/resources/view/nikki/app.js
Normal file
@@ -0,0 +1,193 @@
|
||||
'use strict';
|
||||
'require form';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require poll';
|
||||
'require tools.nikki as nikki';
|
||||
|
||||
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('nikki'),
|
||||
nikki.version(),
|
||||
nikki.status(),
|
||||
nikki.listProfiles()
|
||||
]);
|
||||
},
|
||||
render: function (data) {
|
||||
const subscriptions = uci.sections('nikki', '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('nikki', _('Nikki'), `${_('Transparent Proxy with Mihomo on OpenWrt.')} <a href="https://github.com/nikkinikki-org/OpenWrt-nikki/wiki" target="_blank">${_('How To Use')}</a>`);
|
||||
|
||||
s = m.section(form.TableSection, 'status', _('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(nikki.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 nikki.reload();
|
||||
};
|
||||
|
||||
o = s.option(form.Button, 'restart');
|
||||
o.inputstyle = 'negative';
|
||||
o.inputtitle = _('Restart Service');
|
||||
o.onclick = function () {
|
||||
return nikki.restart();
|
||||
};
|
||||
|
||||
o = s.option(form.Button, 'update_dashboard');
|
||||
o.inputstyle = 'positive';
|
||||
o.inputtitle = _('Update Dashboard');
|
||||
o.onclick = function () {
|
||||
return nikki.updateDashboard();
|
||||
};
|
||||
|
||||
o = s.option(form.Button, 'open_dashboard');
|
||||
o.inputtitle = _('Open Dashboard');
|
||||
o.onclick = function () {
|
||||
return nikki.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, 'cron_expression', _('Cron Expression'));
|
||||
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.placeholder = _('Unlimited');
|
||||
|
||||
o = s.taboption('rlimit', form.Value, 'rlimit_nofile_hard', _('Number of Open Files Hard Limit'));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = _('Unlimited');
|
||||
|
||||
s.tab('environment_variable', _('Environment Variable Config'));
|
||||
|
||||
o = s.taboption('environment_variable', form.DynamicList, 'env_safe_paths', _('Safe Paths'));
|
||||
o.load = function (section_id) {
|
||||
return this.super('load', section_id)?.split(':');
|
||||
};
|
||||
o.write = function (section_id, formvalue) {
|
||||
this.super('write', section_id, formvalue?.join(':'));
|
||||
};
|
||||
|
||||
o = s.taboption('environment_variable', form.Flag, 'env_disable_loopback_detector', _('Disable Loopback Detector'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('environment_variable', form.Flag, 'env_disable_quic_go_gso', _('Disable GSO of quic-go'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('environment_variable', form.Flag, 'env_disable_quic_go_ecn', _('Disable ECN of quic-go'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('environment_variable', form.Flag, 'env_skip_system_ipv6_check', _('Skip System IPv6 Check'));
|
||||
o.rmempty = false;
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
'use strict';
|
||||
'require form';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require fs';
|
||||
'require tools.nikki as nikki';
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return Promise.all([
|
||||
uci.load('nikki'),
|
||||
nikki.listProfiles(),
|
||||
nikki.listRuleProviders(),
|
||||
nikki.listProxyProviders(),
|
||||
]);
|
||||
},
|
||||
render: function (data) {
|
||||
const subscriptions = uci.sections('nikki', 'subscription');
|
||||
const profiles = data[1];
|
||||
const ruleProviders = data[2];
|
||||
const proxyProviders = data[3];
|
||||
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('nikki');
|
||||
|
||||
s = m.section(form.NamedSection, 'editor', 'editor', _('Editor'));
|
||||
|
||||
o = s.option(form.ListValue, '_file', _('Choose File'));
|
||||
o.optional = true;
|
||||
|
||||
for (const profile of profiles) {
|
||||
o.value(nikki.profilesDir + '/' + profile.name, _('File:') + profile.name);
|
||||
};
|
||||
|
||||
for (const subscription of subscriptions) {
|
||||
o.value(nikki.subscriptionsDir + '/' + subscription['.name'] + '.yaml', _('Subscription:') + subscription.name);
|
||||
};
|
||||
|
||||
for (const ruleProvider of ruleProviders) {
|
||||
o.value(nikki.ruleProvidersDir + '/' + ruleProvider.name, _('Rule Provider:') + ruleProvider.name);
|
||||
};
|
||||
|
||||
for (const proxyProvider of proxyProviders) {
|
||||
o.value(nikki.proxyProvidersDir + '/' + proxyProvider.name, _('Proxy Provider:') + proxyProvider.name);
|
||||
};
|
||||
|
||||
o.value(nikki.mixinFilePath, _('File for Mixin'));
|
||||
o.value(nikki.runProfilePath, _('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' ? nikki.reload() : nikki.restart();
|
||||
});
|
||||
},
|
||||
handleReset: null
|
||||
});
|
||||
124
luci-app-nikki/htdocs/luci-static/resources/view/nikki/log.js
Normal file
124
luci-app-nikki/htdocs/luci-static/resources/view/nikki/log.js
Normal file
@@ -0,0 +1,124 @@
|
||||
'use strict';
|
||||
'require form';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require fs';
|
||||
'require poll';
|
||||
'require tools.nikki as nikki';
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return Promise.all([
|
||||
uci.load('nikki'),
|
||||
nikki.getAppLog(),
|
||||
nikki.getCoreLog()
|
||||
]);
|
||||
},
|
||||
render: function (data) {
|
||||
const appLog = data[1];
|
||||
const coreLog = data[2];
|
||||
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('nikki');
|
||||
|
||||
s = m.section(form.NamedSection, 'log', 'log', _('Log'));
|
||||
|
||||
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 nikki.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(nikki.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 nikki.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(nikki.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 nikki.debug().then(function () {
|
||||
fs.read_direct(nikki.debugLogPath, '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();
|
||||
},
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
575
luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js
Normal file
575
luci-app-nikki/htdocs/luci-static/resources/view/nikki/mixin.js
Normal file
@@ -0,0 +1,575 @@
|
||||
'use strict';
|
||||
'require form';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require fs';
|
||||
'require network';
|
||||
'require poll';
|
||||
'require tools.widgets as widgets';
|
||||
'require tools.nikki as nikki';
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return Promise.all([
|
||||
uci.load('nikki'),
|
||||
network.getNetworks(),
|
||||
|
||||
]);
|
||||
},
|
||||
render: function (data) {
|
||||
const networks = data[1];
|
||||
|
||||
let m, s, o, so;
|
||||
|
||||
m = new form.Map('nikki');
|
||||
|
||||
s = m.section(form.NamedSection, 'mixin', 'mixin', _('Mixin Option'));
|
||||
|
||||
s.tab('general', _('General Config'));
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'log_level', _('Log Level'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('silent');
|
||||
o.value('error');
|
||||
o.value('warning');
|
||||
o.value('info');
|
||||
o.value('debug');
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'mode', _('Mode'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('global', _('Global Mode'));
|
||||
o.value('rule', _('Rule Mode'));
|
||||
o.value('direct', _('Direct Mode'));
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'match_process', _('Match Process'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('off');
|
||||
o.value('strict');
|
||||
o.value('always');
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'outbound_interface', _('Outbound Interface'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
for (const network of networks) {
|
||||
if (network.getName() === 'loopback') {
|
||||
continue;
|
||||
}
|
||||
o.value(network.getName());
|
||||
}
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'ipv6', 'IPv6');
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'unify_delay', _('Unify Delay'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'tcp_concurrent', _('TCP Concurrent'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'disable_tcp_keep_alive', _('Disable TCP Keep Alive'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('general', form.Value, 'tcp_keep_alive_idle', _('TCP Keep Alive Idle'));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('general', form.Value, 'tcp_keep_alive_interval', _('TCP Keep Alive Interval'));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('general', form.Value, 'global_client_fingerprint', _('Global Client Fingerprint'));
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('random', _('Random'));
|
||||
o.value('chrome', 'Chrome');
|
||||
o.value('firefox', 'Firefox');
|
||||
o.value('safari', 'Safari');
|
||||
o.value('edge', 'Edge');
|
||||
|
||||
s.tab('external_control', _('External Control Config'));
|
||||
|
||||
o = s.taboption('external_control', form.Value, 'ui_path', _('UI Path'));
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('external_control', form.Value, 'ui_name', _('UI Name'));
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('external_control', form.Value, 'ui_url', _('UI 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, 'api_listen', _('API Listen'));
|
||||
o.datatype = 'ipaddrport(1)';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('external_control', form.Value, 'api_secret', _('API Secret'));
|
||||
o.password = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('external_control', form.ListValue, 'selection_cache', _('Save Proxy Selection'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
s.tab('inbound', _('Inbound Config'));
|
||||
|
||||
o = s.taboption('inbound', form.ListValue, 'allow_lan', _('Allow Lan'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('inbound', form.Value, 'http_port', _('HTTP Port'));
|
||||
o.datatype = 'port';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('inbound', form.Value, 'socks_port', _('SOCKS Port'));
|
||||
o.datatype = 'port';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('inbound', form.Value, 'mixed_port', _('Mixed Port'));
|
||||
o.datatype = 'port';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('inbound', form.Value, 'redir_port', _('Redirect Port'));
|
||||
o.datatype = 'port';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('inbound', form.Value, 'tproxy_port', _('TPROXY Port'));
|
||||
o.datatype = 'port';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('inbound', form.Flag, 'authentication', _('Overwrite Authentication'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('inbound', form.SectionValue, '_authentications', form.TableSection, 'authentication', _('Edit Authentications'));
|
||||
o.retain = true;
|
||||
o.depends('authentication', '1');
|
||||
|
||||
o.subsection.addremove = true;
|
||||
o.subsection.anonymous = true;
|
||||
o.subsection.sortable = true;
|
||||
|
||||
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.Value, 'username', _('Username'));
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.Value, 'password', _('Password'));
|
||||
so.password = true;
|
||||
so.rmempty = false;
|
||||
|
||||
s.tab('tun', _('TUN Config'));
|
||||
|
||||
o = s.taboption('tun', form.ListValue, 'tun_enabled', _('Enable'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('tun', form.Value, 'tun_device', _('Device Name'));
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('tun', form.ListValue, 'tun_stack', _('Stack'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('system', 'System');
|
||||
o.value('gvisor', 'gVisor');
|
||||
o.value('mixed', 'Mixed');
|
||||
|
||||
o = s.taboption('tun', form.Value, 'tun_mtu', _('MTU'));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('tun', form.ListValue, 'tun_gso', _('GSO'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('tun', form.Value, 'tun_gso_max_size', _('GSO Max Size'));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('tun', form.Flag, 'tun_dns_hijack', _('Overwrite DNS Hijack'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('tun', form.DynamicList, 'tun_dns_hijacks', _('Edit DNS Hijacks'));
|
||||
o.retain = true;
|
||||
o.depends('tun_dns_hijack', '1');
|
||||
o.value('tcp://any:53');
|
||||
o.value('udp://any:53');
|
||||
|
||||
s.tab('dns', _('DNS Config'));
|
||||
|
||||
o = s.taboption('dns', form.ListValue, 'dns_enabled', _('Enable'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('dns', form.Value, 'dns_listen', _('DNS Listen'));
|
||||
o.datatype = 'ipaddrport(1)';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('dns', form.ListValue, 'dns_ipv6', 'IPv6');
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('dns', form.ListValue, 'dns_mode', _('DNS Mode'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('redir-host', 'Redir-Host');
|
||||
o.value('fake-ip', 'Fake-IP');
|
||||
|
||||
o = s.taboption('dns', form.Value, 'fake_ip_range', _('Fake-IP Range'));
|
||||
o.datatype = 'cidr4';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('dns', form.Flag, 'fake_ip_filter', _('Overwrite Fake-IP Filter'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('dns', form.DynamicList, 'fake_ip_filters', _('Edit Fake-IP Filters'));
|
||||
o.retain = true;
|
||||
o.depends('fake_ip_filter', '1');
|
||||
|
||||
o = s.taboption('dns', form.ListValue, 'fake_ip_filter_mode', _('Fake-IP Filter Mode'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('blacklist', _('Block Mode'));
|
||||
o.value('whitelist', _('Allow Mode'));
|
||||
|
||||
o = s.taboption('dns', form.ListValue, 'fake_ip_cache', _('Fake-IP Cache'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('dns', form.ListValue, 'dns_respect_rules', _('Respect Rules'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('dns', form.ListValue, 'dns_doh_prefer_http3', _('DoH Prefer HTTP/3'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('dns', form.ListValue, 'dns_system_hosts', _('Use System Hosts'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('dns', form.ListValue, 'dns_hosts', _('Use Hosts'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('dns', form.Flag, 'hosts', _('Overwrite Hosts'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('dns', form.SectionValue, '_hosts', form.TableSection, 'hosts', _('Edit Hosts'));
|
||||
o.retain = true;
|
||||
o.depends('hosts', '1');
|
||||
|
||||
o.subsection.addremove = true;
|
||||
o.subsection.anonymous = true;
|
||||
o.subsection.sortable = true;
|
||||
|
||||
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.Value, 'domain_name', _('Domain Name'));
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.DynamicList, 'ip', 'IP');
|
||||
|
||||
o = s.taboption('dns', form.Flag, 'dns_nameserver', _('Overwrite Nameserver'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('dns', form.SectionValue, '_dns_nameservers', form.TableSection, 'nameserver', _('Edit Nameservers'));
|
||||
o.retain = true;
|
||||
o.depends('dns_nameserver', '1');
|
||||
|
||||
o.subsection.addremove = true;
|
||||
o.subsection.anonymous = true;
|
||||
o.subsection.sortable = true;
|
||||
|
||||
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.ListValue, 'type', _('Type'));
|
||||
so.value('default-nameserver');
|
||||
so.value('proxy-server-nameserver');
|
||||
so.value('direct-nameserver');
|
||||
so.value('nameserver');
|
||||
so.value('fallback');
|
||||
|
||||
so = o.subsection.option(form.DynamicList, 'nameserver', _('Nameserver'));
|
||||
|
||||
o = s.taboption('dns', form.Flag, 'dns_nameserver_policy', _('Overwrite Nameserver Policy'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('dns', form.SectionValue, '_dns_nameserver_policies', form.TableSection, 'nameserver_policy', _('Edit Nameserver Policies'));
|
||||
o.retain = true;
|
||||
o.depends('dns_nameserver_policy', '1');
|
||||
|
||||
o.subsection.addremove = true;
|
||||
o.subsection.anonymous = true;
|
||||
o.subsection.sortable = true;
|
||||
|
||||
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.Value, 'matcher', _('Matcher'));
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.DynamicList, 'nameserver', _('Nameserver'));
|
||||
|
||||
s.tab('sniffer', _('Sniffer Config'));
|
||||
|
||||
o = s.taboption('sniffer', form.ListValue, 'sniffer', _('Enable'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('sniffer', form.ListValue, 'sniffer_sniff_dns_mapping', _('Sniff Redir-Host'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('sniffer', form.ListValue, 'sniffer_sniff_pure_ip', _('Sniff Pure IP'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('sniffer', form.Flag, 'sniffer_force_domain_name', _('Overwrite Force Sniff Domain Name'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('sniffer', form.DynamicList, 'sniffer_force_domain_names', _('Force Sniff Domain Name'));
|
||||
o.retain = true;
|
||||
o.depends('sniffer_force_domain_name', '1');
|
||||
|
||||
o = s.taboption('sniffer', form.Flag, 'sniffer_ignore_domain_name', _('Overwrite Ignore Sniff Domain Name'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('sniffer', form.DynamicList, 'sniffer_ignore_domain_names', _('Ignore Sniff Domain Name'));
|
||||
o.retain = true;
|
||||
o.depends('sniffer_ignore_domain_name', '1');
|
||||
|
||||
o = s.taboption('sniffer', form.Flag, 'sniffer_sniff', _('Overwrite Sniff By Protocol'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('sniffer', form.SectionValue, '_sniffer_sniffs', form.TableSection, 'sniff', _('Sniff By Protocol'));
|
||||
o.retain = true;
|
||||
o.depends('sniffer_sniff', '1');
|
||||
|
||||
o.subsection.anonymous = true;
|
||||
o.subsection.addremove = false;
|
||||
|
||||
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.ListValue, 'protocol', _('Protocol'));
|
||||
so.value('HTTP');
|
||||
so.value('TLS');
|
||||
so.value('QUIC');
|
||||
so.readonly = true;
|
||||
|
||||
so = o.subsection.option(form.DynamicList, 'port', _('Port'));
|
||||
so.datatype = 'portrange';
|
||||
|
||||
so = o.subsection.option(form.Flag, 'overwrite_destination', _('Overwrite Destination'));
|
||||
so.rmempty = false;
|
||||
|
||||
s.tab('rule', _('Rule Config'));
|
||||
|
||||
o = s.taboption('rule', form.Flag, 'rule_provider', _('Append Rule Provider'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('rule', form.SectionValue, '_rule_providers', form.GridSection, 'rule_provider', _('Edit Rule Providers'));
|
||||
o.retain = true;
|
||||
o.depends('rule_provider', '1');
|
||||
|
||||
o.subsection.anonymous = true;
|
||||
o.subsection.addremove = true;
|
||||
o.subsection.sortable = true;
|
||||
|
||||
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
|
||||
so.default = 1;
|
||||
so.editable = true;
|
||||
so.modalonly = false;
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.Value, 'name', _('Name'));
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.ListValue, 'type', _('Type'));
|
||||
so.default = 'http';
|
||||
so.rmempty = false;
|
||||
so.value('http');
|
||||
so.value('file');
|
||||
|
||||
so = o.subsection.option(form.Value, 'url', _('Url'));
|
||||
so.modalonly = true;
|
||||
so.rmempty = false;
|
||||
so.depends('type', 'http');
|
||||
|
||||
so = o.subsection.option(form.Value, 'node', _('Node'));
|
||||
so.default = 'DIRECT';
|
||||
so.modalonly = true;
|
||||
so.depends('type', 'http');
|
||||
so.value('GLOBAL');
|
||||
so.value('DIRECT');
|
||||
|
||||
so = o.subsection.option(form.Value, 'file_size_limit', _('File Size Limit'));
|
||||
so.datatype = 'uinteger';
|
||||
so.default = 0;
|
||||
so.modalonly = true;
|
||||
so.depends('type', 'http');
|
||||
|
||||
so = o.subsection.option(form.FileUpload, 'file_path', _('File Path'));
|
||||
so.modalonly = true;
|
||||
so.rmempty = false;
|
||||
so.root_directory = nikki.ruleProvidersDir;
|
||||
so.depends('type', 'file');
|
||||
|
||||
so = o.subsection.option(form.ListValue, 'file_format', _('File Format'));
|
||||
so.default = 'yaml';
|
||||
so.value('mrs');
|
||||
so.value('yaml');
|
||||
so.value('text');
|
||||
|
||||
so = o.subsection.option(form.ListValue, 'behavior', _('Behavior'));
|
||||
so.default = 'classical';
|
||||
so.rmempty = false;
|
||||
so.value('classical');
|
||||
so.value('domain');
|
||||
so.value('ipcidr');
|
||||
|
||||
so = o.subsection.option(form.Value, 'update_interval', _('Update Interval'));
|
||||
so.datatype = 'uinteger';
|
||||
so.default = 0;
|
||||
so.modalonly = true;
|
||||
so.depends('type', 'http');
|
||||
|
||||
o = s.taboption('rule', form.Flag, 'rule', _('Append Rule'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('rule', form.SectionValue, '_rules', form.TableSection, 'rule', _('Edit Rules'));
|
||||
o.retain = true;
|
||||
o.depends('rule', '1');
|
||||
|
||||
o.subsection.anonymous = true;
|
||||
o.subsection.addremove = true;
|
||||
o.subsection.sortable = true;
|
||||
|
||||
so = o.subsection.option(form.Flag, 'enabled', _('Enable'));
|
||||
so.default = 1;
|
||||
so.rmempty = false;
|
||||
|
||||
so = o.subsection.option(form.Value, 'type', _('Type'));
|
||||
so.rmempty = false;
|
||||
so.value('RULE-SET', _('Rule Set'));
|
||||
so.value('DOMAIN', _('Domain Name'));
|
||||
so.value('DOMAIN-SUFFIX', _('Domain Name Suffix'));
|
||||
so.value('DOMAIN-WILDCARD', _('Domain Name Wildcard'));
|
||||
so.value('DOMAIN-KEYWORD', _('Domain Name Keyword'));
|
||||
so.value('DOMAIN-REGEX', _('Domain Name Regex'));
|
||||
so.value('IP-CIDR', _('Destination IP'));
|
||||
so.value('DST-PORT', _('Destination Port'));
|
||||
so.value('PROCESS-NAME', _('Process Name'));
|
||||
so.value('GEOSITE', _('Domain Name Geo'));
|
||||
so.value('GEOIP', _('Destination IP Geo'));
|
||||
|
||||
so = o.subsection.option(form.Value, 'matcher', _('Matcher'));
|
||||
so.rmempty = false;
|
||||
so.depends({ 'type': /MATCH/i, '!reverse': true });
|
||||
|
||||
so = o.subsection.option(form.Value, 'node', _('Node'));
|
||||
so.default = 'GLOBAL';
|
||||
so.value('GLOBAL');
|
||||
so.value('DIRECT');
|
||||
so.value('REJECT');
|
||||
so.value('REJECT-DROP');
|
||||
|
||||
so = o.subsection.option(form.Flag, 'no_resolve', _('No Resolve'));
|
||||
so.rmempty = false;
|
||||
so.depends('type', /IP-CIDR6?/i);
|
||||
so.depends('type', /IP-ASN/i);
|
||||
so.depends('type', /GEOIP/i);
|
||||
|
||||
s.tab('geox', _('GeoX Config'));
|
||||
|
||||
o = s.taboption('geox', form.ListValue, 'geoip_format', _('GeoIP Format'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('dat', 'DAT');
|
||||
o.value('mmdb', 'MMDB');
|
||||
|
||||
o = s.taboption('geox', form.ListValue, 'geodata_loader', _('GeoData Loader'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('standard', _('Standard Loader'));
|
||||
o.value('memconservative', _('Memory Conservative Loader'));
|
||||
|
||||
o = s.taboption('geox', form.Value, 'geosite_url', _('GeoSite Url'));
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('geox', form.Value, 'geoip_mmdb_url', _('GeoIP(MMDB) Url'));
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('geox', form.Value, 'geoip_dat_url', _('GeoIP(DAT) Url'));
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('geox', form.Value, 'geoip_asn_url', _('GeoIP(ASN) Url'));
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
o = s.taboption('geox', form.ListValue, 'geox_auto_update', _('GeoX Auto Update'));
|
||||
o.optional = true;
|
||||
o.placeholder = _('Unmodified');
|
||||
o.value('0', _('Disable'));
|
||||
o.value('1', _('Enable'));
|
||||
|
||||
o = s.taboption('geox', form.Value, 'geox_update_interval', _('GeoX Update Interval'));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = _('Unmodified');
|
||||
|
||||
s.tab('mixin_file_content', _('Mixin File Content'));
|
||||
|
||||
o = s.taboption('mixin_file_content', form.Flag, 'mixin_file_content', _('Enable'), _('Please go to the editor tab to edit the file for mixin'));
|
||||
o.rmempty = false;
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
'use strict';
|
||||
'require form';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require tools.nikki as nikki';
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return Promise.all([
|
||||
uci.load('nikki')
|
||||
]);
|
||||
},
|
||||
render: function (data) {
|
||||
let m, s, o, so;
|
||||
|
||||
m = new form.Map('nikki');
|
||||
|
||||
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 = nikki.profilesDir;
|
||||
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 nikki.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 = 'clash';
|
||||
o.modalonly = true;
|
||||
o.rmempty = false;
|
||||
o.value('clash');
|
||||
o.value('clash.meta');
|
||||
o.value('mihomo');
|
||||
|
||||
o = s.option(form.ListValue, 'prefer', _('Prefer'));
|
||||
o.default = 'remote';
|
||||
o.modalonly = true;
|
||||
o.value('remote', _('Remote'));
|
||||
o.value('local', _('Local'));
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
194
luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js
Normal file
194
luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js
Normal file
@@ -0,0 +1,194 @@
|
||||
'use strict';
|
||||
'require form';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require network';
|
||||
'require tools.widgets as widgets';
|
||||
'require tools.nikki as nikki';
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return Promise.all([
|
||||
uci.load('nikki'),
|
||||
network.getHostHints(),
|
||||
network.getNetworks(),
|
||||
nikki.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('nikki');
|
||||
|
||||
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.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'));
|
||||
|
||||
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;
|
||||
|
||||
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)';
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
1011
luci-app-nikki/po/templates/nikki.pot
Normal file
1011
luci-app-nikki/po/templates/nikki.pot
Normal file
File diff suppressed because it is too large
Load Diff
1
luci-app-nikki/po/zh-cn
Symbolic link
1
luci-app-nikki/po/zh-cn
Symbolic link
@@ -0,0 +1 @@
|
||||
zh_Hans
|
||||
1018
luci-app-nikki/po/zh_Hans/nikki.po
Normal file
1018
luci-app-nikki/po/zh_Hans/nikki.po
Normal file
File diff suppressed because it is too large
Load Diff
1021
luci-app-nikki/po/zh_Hant/nikki.po
Normal file
1021
luci-app-nikki/po/zh_Hant/nikki.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"admin/services/nikki": {
|
||||
"title": "Nikki",
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-nikki" ],
|
||||
"uci": { "nikki": true }
|
||||
}
|
||||
},
|
||||
"admin/services/nikki/config": {
|
||||
"title": "App Config",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "nikki/app"
|
||||
}
|
||||
},
|
||||
"admin/services/nikki/profile": {
|
||||
"title": "Profile",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "nikki/profile"
|
||||
}
|
||||
},
|
||||
"admin/services/nikki/mixin": {
|
||||
"title": "Mixin Config",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "nikki/mixin"
|
||||
}
|
||||
},
|
||||
"admin/services/nikki/proxy": {
|
||||
"title": "Proxy Config",
|
||||
"order": 40,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "nikki/proxy"
|
||||
}
|
||||
},
|
||||
"admin/services/nikki/editor": {
|
||||
"title": "Editor",
|
||||
"order": 50,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "nikki/editor"
|
||||
}
|
||||
},
|
||||
"admin/services/nikki/log": {
|
||||
"title": "Log",
|
||||
"order": 60,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "nikki/log"
|
||||
}
|
||||
}
|
||||
}
|
||||
37
luci-app-nikki/root/usr/share/rpcd/acl.d/luci-app-nikki.json
Normal file
37
luci-app-nikki/root/usr/share/rpcd/acl.d/luci-app-nikki.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"luci-app-nikki": {
|
||||
"description": "Grant access to nikki procedures",
|
||||
"read": {
|
||||
"uci": [ "nikki" ],
|
||||
"ubus": {
|
||||
"rc": [ "*" ],
|
||||
"luci.nikki": [ "*" ]
|
||||
},
|
||||
"file": {
|
||||
"/etc/nikki/profiles/*.yaml": ["read"],
|
||||
"/etc/nikki/profiles/*.yml": ["read"],
|
||||
"/etc/nikki/subscriptions/*.yaml": ["read"],
|
||||
"/etc/nikki/subscriptions/*.yml": ["read"],
|
||||
"/etc/nikki/mixin.yaml": ["read"],
|
||||
"/etc/nikki/run/config.yaml": ["read"],
|
||||
"/etc/nikki/run/providers/rule/*": ["read"],
|
||||
"/etc/nikki/run/providers/proxy/*": ["read"],
|
||||
"/var/log/nikki/*.log": ["read"]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "nikki" ],
|
||||
"file": {
|
||||
"/etc/nikki/profiles/*.yaml": ["write"],
|
||||
"/etc/nikki/profiles/*.yml": ["write"],
|
||||
"/etc/nikki/subscriptions/*.yaml": ["write"],
|
||||
"/etc/nikki/subscriptions/*.yml": ["write"],
|
||||
"/etc/nikki/mixin.yaml": ["write"],
|
||||
"/etc/nikki/run/config.yaml": ["write"],
|
||||
"/etc/nikki/run/providers/rule/*": ["write"],
|
||||
"/etc/nikki/run/providers/proxy/*": ["write"],
|
||||
"/var/log/nikki/*.log": ["write"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
luci-app-nikki/root/usr/share/rpcd/ucode/luci.nikki
Normal file
110
luci-app-nikki/root/usr/share/rpcd/ucode/luci.nikki
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/ucode
|
||||
|
||||
'use strict';
|
||||
|
||||
import { access, popen, writefile } from 'fs';
|
||||
import { get_users, get_groups, get_cgroups, load_profile } from '/etc/nikki/ucode/include.uc';
|
||||
|
||||
const methods = {
|
||||
version: {
|
||||
call: function() {
|
||||
let process;
|
||||
let app = '';
|
||||
if (system('command -v opkg') == 0) {
|
||||
process = popen('opkg list-installed luci-app-nikki | cut -d " " -f 3');
|
||||
if (process) {
|
||||
app = trim(process.read('all'));
|
||||
process.close();
|
||||
}
|
||||
} else if (system('command -v apk') == 0) {
|
||||
process = popen('apk list -I luci-app-nikki | cut -d " " -f 1 | cut -d "-" -f 4');
|
||||
if (process) {
|
||||
app = trim(process.read('all'));
|
||||
process.close();
|
||||
}
|
||||
}
|
||||
let core = '';
|
||||
process = popen('mihomo -v | grep Mihomo | cut -d " " -f 3');
|
||||
if (process) {
|
||||
core = trim(process.read('all'));
|
||||
process.close();
|
||||
}
|
||||
return { app: app, core: core };
|
||||
}
|
||||
},
|
||||
profile: {
|
||||
args: { defaults: {} },
|
||||
call: function(req) {
|
||||
let profile = {};
|
||||
const defaults = req.args?.defaults ?? {};
|
||||
const filepath = '/etc/nikki/run/config.yaml';
|
||||
const tmpFilepath = '/var/run/nikki/profile.json';
|
||||
if (access(filepath, 'r')) {
|
||||
writefile(tmpFilepath, defaults);
|
||||
const command = `yq -p yaml -o json eval-all 'select(fi == 0) *? select(fi == 1)' ${tmpFilepath} ${filepath}`;
|
||||
const process = popen(command);
|
||||
if (process) {
|
||||
profile = json(process);
|
||||
process.close();
|
||||
}
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
},
|
||||
update_subscription: {
|
||||
args: { section_id: 'section_id' },
|
||||
call: function(req) {
|
||||
let success = false;
|
||||
const section_id = req.args?.section_id;
|
||||
if (section_id) {
|
||||
success = system(['service', 'nikki', 'update_subscription', section_id]) == 0;
|
||||
}
|
||||
return { success: success };
|
||||
}
|
||||
},
|
||||
api: {
|
||||
args: { method: 'method', path: 'path', query: 'query', body: 'body' },
|
||||
call: function(req) {
|
||||
let result = {};
|
||||
|
||||
const method = req.args?.method;
|
||||
const path = req.args?.path;
|
||||
const query = req.args?.query;
|
||||
const body = req.args?.body;
|
||||
|
||||
const profile = load_profile();
|
||||
const api_listen = profile['external-controller'];
|
||||
const api_secret = profile['secret'];
|
||||
|
||||
if (!api_listen) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const url = api_listen + path;
|
||||
|
||||
const process = popen(`curl --request '${method}' --oauth2-bearer '${api_secret}' --url-query '${query}' --data '${body}' '${url}'`);
|
||||
if (process) {
|
||||
result = json(process);
|
||||
process.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
get_identifiers: {
|
||||
call: function() {
|
||||
const users = filter(get_users(), (x) => x != '');
|
||||
const groups = filter(get_groups(), (x) => x != '');
|
||||
const cgroups = filter(get_cgroups(), (x) => x != '' && index(x, 'services/nikki') < 0);
|
||||
return { users: users, groups: groups, cgroups: cgroups };
|
||||
}
|
||||
},
|
||||
debug: {
|
||||
call: function() {
|
||||
const success = system('/etc/nikki/scripts/debug.sh > /var/log/nikki/debug.log') == 0;
|
||||
return { success: success };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { 'luci.nikki': methods };
|
||||
Reference in New Issue
Block a user