luci-app-mihomo: sync upstream

last commit: 30b31d9d65
This commit is contained in:
gitea-action 2024-09-25 05:44:33 +08:00
parent 7868a95774
commit 300ac6e0dd
9 changed files with 1330 additions and 0 deletions

10
luci-app-mihomo/Makefile Normal file
View File

@ -0,0 +1,10 @@
include $(TOPDIR)/rules.mk
PKG_VERSION:=1.8.3
LUCI_TITLE:=LuCI Support for mihomo
LUCI_DEPENDS:=+luci-base +mihomo
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,113 @@
'use strict';
'require baseclass';
'require uci';
'require fs';
'require rpc';
const homeDir = '/etc/mihomo';
const profilesDir = `${homeDir}/profiles`;
const mixinFilePath = `${homeDir}/mixin.yaml`;
const runDir = `${homeDir}/run`;
const runAppLogPath = `${runDir}/app.log`;
const runCoreLogPath = `${runDir}/core.log`;
const runProfilePath = `${runDir}/config.yaml`;
const nftDir = `${homeDir}/nftables`;
const reservedIPNFT = `${nftDir}/reserved_ip.nft`;
const reservedIP6NFT = `${nftDir}/reserved_ip6.nft`;
return baseclass.extend({
homeDir: homeDir,
profilesDir: profilesDir,
mixinFilePath: mixinFilePath,
runDir: runDir,
runAppLogPath: runAppLogPath,
runCoreLogPath: runCoreLogPath,
runProfilePath: runProfilePath,
reservedIPNFT: reservedIPNFT,
reservedIP6NFT: reservedIP6NFT,
callServiceList: rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
}),
getAppLog: function () {
return L.resolveDefault(fs.read_direct(this.runAppLogPath));
},
getCoreLog: function () {
return L.resolveDefault(fs.read_direct(this.runCoreLogPath));
},
clearAppLog: function () {
return fs.exec_direct('/usr/libexec/mihomo-call', ['clear', 'app_log']);
},
clearCoreLog: function () {
return fs.exec_direct('/usr/libexec/mihomo-call', ['clear', 'core_log']);
},
listProfiles: function () {
return L.resolveDefault(fs.list(this.profilesDir), []);
},
status: async function () {
try {
return (await this.callServiceList('mihomo'))['mihomo']['instances']['mihomo']['running'];
} catch (ignored) {
return false;
}
},
reload: function () {
return fs.exec_direct('/usr/libexec/mihomo-call', ['service', 'reload']);
},
restart: function () {
return fs.exec_direct('/usr/libexec/mihomo-call', ['service', 'restart']);
},
appVersion: function () {
return L.resolveDefault(fs.exec_direct('/usr/libexec/mihomo-call', ['version', 'app']), 'Unknown');
},
coreVersion: function () {
return L.resolveDefault(fs.exec_direct('/usr/libexec/mihomo-call', ['version', 'core']), 'Unknown');
},
callMihomoAPI: async function (method, path, body) {
const running = await this.status();
if (running) {
const apiPort = uci.get('mihomo', 'mixin', 'api_port');
const apiSecret = uci.get('mihomo', 'mixin', 'api_secret');
const url = `http://${window.location.hostname}:${apiPort}${path}`;
await fetch(url, {
method: method,
headers: { 'Authorization': `Bearer ${apiSecret}` },
body: body
})
} else {
alert(_('Service is not running.'));
}
},
openDashboard: async function () {
const running = await this.status();
if (running) {
const uiName = uci.get('mihomo', 'mixin', 'ui_name');
const apiPort = uci.get('mihomo', 'mixin', 'api_port');
const apiSecret = uci.get('mihomo', 'mixin', 'api_secret');
let url;
if (uiName) {
url = `http://${window.location.hostname}:${apiPort}/ui/${uiName}/?host=${window.location.hostname}&hostname=${window.location.hostname}&port=${apiPort}&secret=${apiSecret}`;
} else {
url = `http://${window.location.hostname}:${apiPort}/ui/?host=${window.location.hostname}&hostname=${window.location.hostname}&port=${apiPort}&secret=${apiSecret}`;
}
setTimeout(() => window.open(url, '_blank'), 0);
} else {
alert(_('Service is not running.'));
}
},
})

View File

@ -0,0 +1,515 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require network';
'require rpc';
'require poll';
'require tools.widgets as widgets';
'require tools.mihomo as mihomo';
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('mihomo'),
mihomo.listProfiles(),
mihomo.appVersion(),
mihomo.coreVersion(),
mihomo.status(),
network.getHostHints(),
]);
},
render: function (data) {
const subscriptions = uci.sections('mihomo', 'subscription');
const profiles = data[1];
const appVersion = data[2];
const coreVersion = data[3];
const running = data[4];
const hosts = data[5].hosts;
let m, s, o, so;
m = new form.Map('mihomo', _('MihomoTProxy'), `${_('Transparent Proxy with Mihomo on OpenWrt.')} <a href="https://github.com/morytyann/OpenWrt-mihomo/wiki" target="_blank">${_('How To Use')}</a>`);
s = m.section(form.NamedSection, 'status', 'status', _('Status'));
o = s.option(form.Value, '_app_version', _('App Version'));
o.readonly = true;
o.load = function (section_id) {
return appVersion.trim();
};
o.write = function () { };
o = s.option(form.Value, '_core_version', _('Core Version'));
o.readonly = true;
o.load = function (section_id) {
return coreVersion.trim();
};
o.write = function () { };
o = s.option(form.DummyValue, '_core_status', _('Core Status'));
o.cfgvalue = function (section_id) {
return renderStatus(running);
};
poll.add(function () {
return L.resolveDefault(mihomo.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 mihomo.reload();
};
o = s.option(form.Button, 'restart', '-');
o.inputstyle = 'negative';
o.inputtitle = _('Restart Service');
o.onclick = function () {
return mihomo.restart();
};
o = s.option(form.Button, 'update_dashboard', '-');
o.inputstyle = 'positive';
o.inputtitle = _('Update Dashboard');
o.onclick = function () {
return mihomo.callMihomoAPI('POST', '/upgrade/ui');
};
o = s.option(form.Button, 'open_dashboard', '-');
o.inputtitle = _('Open Dashboard');
o.onclick = function () {
return mihomo.openDashboard();
};
s = m.section(form.NamedSection, 'config', 'config', _('Basic Config'));
o = s.option(form.Flag, 'enabled', _('Enable'));
o.rmempty = false;
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.ListValue, 'profile', _('Choose Profile'));
o.rmempty = false;
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.FileUpload, 'upload_profile', _('Upload Profile'));
o.root_directory = mihomo.profilesDir;
o = s.option(form.Flag, 'mixin', _('Mixin'));
o.rmempty = false;
o = s.option(form.Flag, 'test_profile', _('Test Profile'));
o.rmempty = false;
o = s.option(form.Flag, 'fast_reload', _('Fast Reload'));
o.rmempty = false;
s = m.section(form.NamedSection, 'proxy', 'proxy', _('Proxy Config'));
s.tab('transparent_proxy', _('Transparent Proxy'));
o = s.taboption('transparent_proxy', form.Flag, 'transparent_proxy', _('Enable'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.ListValue, 'tcp_transparent_proxy_mode', _('TCP Proxy Mode'));
o.value('redirect', _('Redirect Mode'));
o.value('tproxy', _('TPROXY Mode'));
o.value('tun', _('TUN Mode'));
o = s.taboption('transparent_proxy', form.ListValue, 'udp_transparent_proxy_mode', _('UDP Proxy Mode'));
o.value('tproxy', _('TPROXY Mode'));
o.value('tun', _('TUN Mode'));
o = s.taboption('transparent_proxy', form.Flag, 'ipv4_dns_hijack', _('IPv4 DNS Hijack'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.Flag, 'ipv6_dns_hijack', _('IPv6 DNS Hijack'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.Flag, 'ipv4_proxy', _('IPv4 Proxy'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.Flag, 'ipv6_proxy', _('IPv6 Proxy'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.Flag, 'router_proxy', _('Router Proxy'));
o.rmempty = false;
o = s.taboption('transparent_proxy', form.Flag, 'lan_proxy', _('Lan Proxy'));
o.rmempty = false;
s.tab('access_control', _('Access Control'));
o = s.taboption('access_control', form.ListValue, 'access_control_mode', _('Mode'));
o.value('all', _('All Mode'));
o.value('allow', _('Allow Mode'));
o.value('block', _('Block Mode'));
o = s.taboption('access_control', form.DynamicList, 'acl_ip', 'IP');
o.datatype = 'ipmask4';
o.retain = true;
o.depends('access_control_mode', 'allow');
o.depends('access_control_mode', 'block');
for (const mac in hosts) {
const host = hosts[mac];
for (const ip of host.ipaddrs) {
const hint = host.name || mac;
o.value(ip, hint ? '%s (%s)'.format(ip, hint) : ip);
}
}
o = s.taboption('access_control', form.DynamicList, 'acl_ip6', 'IP6');
o.datatype = 'ipmask6';
o.retain = true;
o.depends('access_control_mode', 'allow');
o.depends('access_control_mode', 'block');
for (const mac in hosts) {
const host = hosts[mac];
for (const ip of host.ip6addrs) {
const hint = host.name || mac;
o.value(ip, hint ? '%s (%s)'.format(ip, hint) : ip);
}
}
o = s.taboption('access_control', form.DynamicList, 'acl_mac', 'MAC');
o.datatype = 'macaddr';
o.retain = true;
o.depends('access_control_mode', 'allow');
o.depends('access_control_mode', 'block');
for (const mac in hosts) {
const host = hosts[mac];
const hint = host.name || host.ipaddrs[0];
o.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
}
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.Value, 'acl_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 993 995 8080 8443', _('Commonly Used Port'));
o = s.taboption('bypass', form.Value, 'acl_udp_dport', _('Destination UDP Port to Proxy'));
o.rmempty = false;
o.value('0-65535', _('All Port'));
o.value('123 443 8443', _('Commonly Used Port'));
s = m.section(form.TableSection, 'subscription', _('Subscription Config'));
s.addremove = true;
s.anonymous = true;
o = s.option(form.Value, 'name', _('Subscription Name'));
o.rmempty = false;
o.width = '15%';
o = s.option(form.Value, 'url', _('Subscription Url'));
o.rmempty = false;
o = s.option(form.Value, 'user_agent', _('User Agent'));
o.default = 'mihomo';
o.rmempty = false;
o.width = '15%';
o.value('mihomo');
o.value('clash.meta');
o.value('clash');
s = m.section(form.NamedSection, 'mixin', 'mixin', _('Mixin Config'));
s.tab('general', _('General Config'));
o = s.taboption('general', form.ListValue, 'log_level', _('Log Level'));
o.value('silent');
o.value('error');
o.value('warning');
o.value('info');
o.value('debug');
o = s.taboption('general', form.ListValue, 'mode', _('Proxy Mode'));
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.value('strict', _('Auto'));
o.value('always', _('Enable'));
o.value('off', _('Disable'));
o = s.taboption('general', widgets.NetworkSelect, 'outbound_interface', _('Outbound Interface'));
o.optional = true;
o.rmempty = false;
o = s.taboption('general', form.Flag, 'ipv6', _('IPv6'));
o.rmempty = false;
o = s.taboption('general', form.Value, 'tcp_keep_alive_idle', _('TCP Keep Alive Idle'));
o.datatype = 'integer';
o.placeholder = '600';
o = s.taboption('general', form.Value, 'tcp_keep_alive_interval', _('TCP Keep Alive Interval'));
o.datatype = 'integer';
o.placeholder = '15';
s.tab('external_control', _('External Control Config'));
o = s.taboption('external_control', form.Value, 'ui_name', _('UI Name'));
o.rmempty = false;
o = s.taboption('external_control', form.Value, 'ui_url', _('UI Url'));
o.rmempty = false;
o.value('https://mirror.ghproxy.com/https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip', 'MetaCubeXD')
o.value('https://mirror.ghproxy.com/https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip', 'YACD')
o.value('https://mirror.ghproxy.com/https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip', 'Razord')
o = s.taboption('external_control', form.Value, 'api_port', _('API Port'));
o.datatype = 'port';
o.placeholder = '9090';
o = s.taboption('external_control', form.Value, 'api_secret', _('API Secret'));
o.rmempty = false;
o = s.taboption('external_control', form.Flag, 'selection_cache', _('Save Proxy Selection'));
o.rmempty = false;
s.tab('inbound', _('Inbound Config'));
o = s.taboption('inbound', form.Flag, 'allow_lan', _('Allow Lan'));
o.rmempty = false;
o = s.taboption('inbound', form.Value, 'http_port', _('HTTP Port'));
o.datatype = 'port';
o.placeholder = '8080';
o = s.taboption('inbound', form.Value, 'socks_port', _('SOCKS Port'));
o.datatype = 'port';
o.placeholder = '1080';
o = s.taboption('inbound', form.Value, 'mixed_port', _('Mixed Port'));
o.datatype = 'port';
o.placeholder = '7890';
o = s.taboption('inbound', form.Value, 'redir_port', _('Redirect Port'));
o.datatype = 'port';
o.placeholder = '7891';
o = s.taboption('inbound', form.Value, 'tproxy_port', _('TPROXY Port'));
o.datatype = 'port';
o.placeholder = '7892';
o = s.taboption('inbound', form.Flag, 'authentication', _('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.anonymous = true;
o.subsection.addremove = 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.rmempty = false;
s.tab('tun', _('TUN Config'));
o = s.taboption('tun', form.ListValue, 'tun_stack', _('Stack'));
o.value('system', 'System');
o.value('gvisor', 'gVisor');
o.value('mixed', 'Mixed');
o = s.taboption('tun', form.Value, 'tun_mtu', _('MTU'));
o.placeholder = '9000';
o = s.taboption('tun', form.Flag, 'tun_gso', _('GSO'));
o.rmempty = false;
o = s.taboption('tun', form.Value, 'tun_gso_max_size', _('GSO Max Size'));
o.placeholder = '65536';
o.depends('tun_gso', '1');
o = s.taboption('tun', form.Flag, 'tun_endpoint_independent_nat', _('Endpoint Independent NAT'));
o.rmempty = false;
s.tab('dns', _('DNS Config'));
o = s.taboption('dns', form.Value, 'dns_port', _('DNS Port'));
o.datatype = 'port';
o.placeholder = '1053';
o = s.taboption('dns', form.ListValue, 'dns_mode', _('DNS Mode'));
o.value('normal', 'Normal');
o.value('fake-ip', 'Fake-IP');
o.value('redir-host', 'Redir-Host');
o = s.taboption('dns', form.Value, 'fake_ip_range', _('Fake-IP Range'));
o.datatype = 'cidr4';
o.placeholder = '198.18.0.1/16';
o.retain = true;
o.depends('dns_mode', 'fake-ip');
o = s.taboption('dns', form.Flag, 'fake_ip_filter', _('Overwrite Fake-IP Filter'));
o.retain = true;
o.rmempty = false;
o.depends('dns_mode', 'fake-ip');
o = s.taboption('dns', form.DynamicList, 'fake_ip_filters', _('Edit Fake-IP Filters'));
o.retain = true;
o.depends({ 'dns_mode': 'fake-ip', 'fake_ip_filter': '1' });
o = s.taboption('dns', form.ListValue, 'fake_ip_filter_mode', _('Fake-IP Filter Mode'))
o.value('blacklist', _('Block Mode'));
o.value('whitelist', _('Allow Mode'));
o.depends({ 'dns_mode': 'fake-ip', 'fake_ip_filter': '1' });
o = s.taboption('dns', form.Flag, 'fake_ip_cache', _('Fake-IP Cache'));
o.retain = true;
o.rmempty = false;
o.depends('dns_mode', 'fake-ip');
o = s.taboption('dns', form.Flag, 'dns_respect_rules', _('Respect Rules'));
o.rmempty = false;
o = s.taboption('dns', form.Flag, 'dns_ipv6', _('IPv6'));
o.rmempty = false;
o = s.taboption('dns', form.Flag, 'dns_system_hosts', _('Use System Hosts'));
o.rmempty = false;
o = s.taboption('dns', form.Flag, 'dns_hosts', _('Use Hosts'));
o.rmempty = false;
o = s.taboption('dns', form.Flag, 'hosts', _('Overwrite Hosts'));
o.rmempty = false;
o = s.taboption('dns', form.SectionValue, '_hosts', form.TableSection, 'host', _('Edit Hosts'));
o.retain = true;
o.depends('hosts', '1');
o.subsection.anonymous = true;
o.subsection.addremove = 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_nameserver', form.TableSection, 'nameserver', _('Edit Nameservers'));
o.retain = true;
o.depends('dns_nameserver', '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, 'type', _('Type'));
so.readonly = true;
so.value('default-nameserver');
so.value('proxy-server-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.anonymous = true;
o.subsection.addremove = 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('geox', _('GeoX Config'));
o = s.taboption('geox', form.ListValue, 'geoip_format', _('GeoIP Format'));
o.value('dat', 'DAT');
o.value('mmdb', 'MMDB');
o = s.taboption('geox', form.ListValue, 'geodata_loader', _('GeoData Loader'));
o.value('standard', _('Standard Loader'));
o.value('memconservative', _('Memory Conservative Loader'));
o = s.taboption('geox', form.Value, 'geosite_url', _('GeoSite Url'));
o.rmempty = false;
o = s.taboption('geox', form.Value, 'geoip_mmdb_url', _('GeoIP(MMDB) Url'));
o.rmempty = false;
o = s.taboption('geox', form.Value, 'geoip_dat_url', _('GeoIP(DAT) Url'));
o.rmempty = false;
o = s.taboption('geox', form.Value, 'geoip_asn_url', _('GeoIP(ASN) Url'));
o.rmempty = false;
o = s.taboption('geox', form.Flag, 'geox_auto_update', _('GeoX Auto Update'));
o.rmempty = false;
o = s.taboption('geox', form.Value, 'geox_update_interval', _('GeoX Update Interval'), _('Hour'));
o.datatype = 'integer';
o.placeholder = '24';
o.retain = true;
o.depends('geox_auto_update', '1');
s.tab('mixin_file_content', _('Mixin File Content'), _('Please go to the editor tab to edit the file for mixin'));
o = s.taboption('mixin_file_content', form.HiddenValue, '_mixin_file_content');
return m.render();
}
});

View File

@ -0,0 +1,63 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require tools.mihomo as mihomo'
return view.extend({
load: function () {
return Promise.all([
uci.load('mihomo'),
mihomo.listProfiles(),
]);
},
render: function (data) {
const profiles = data[1];
let m, s, o;
m = new form.Map('mihomo');
s = m.section(form.NamedSection, 'editor', 'editor');
o = s.option(form.ListValue, '_profile', _('Choose Profile'));
o.optional = true;
for (const profile of profiles) {
o.value(mihomo.profilesDir + '/' + profile.name, _('File:') + profile.name);
}
o.value(mihomo.mixinFilePath, _('File for Mixin'));
o.value(mihomo.runProfilePath, _('Profile for Startup'));
o.value(mihomo.reservedIPNFT, _('File for Reserved IP'));
o.value(mihomo.reservedIP6NFT, _('File for Reserved IP6'));
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('mihomo.editor._profile_content')[0].getUIElement('editor').setValue(content);
});
};
o = s.option(form.TextValue, '_profile_content',);
o.rows = 25;
o.write = function (section_id, formvalue) {
const path = m.lookupOption('mihomo.editor._profile')[0].formvalue('editor');
return fs.write(path, formvalue);
};
o.remove = function (section_id) {
const path = m.lookupOption('mihomo.editor._profile')[0].formvalue('editor');
return fs.write(path);
};
return m.render();
},
handleSaveApply: function (ev, mode) {
return this.handleSave(ev).finally(function() {
return mode === '0' ? mihomo.reload() : mihomo.restart();
});
},
handleReset: null
});

View File

@ -0,0 +1,98 @@
'use strict';
'require form';
'require view';
'require uci';
'require fs';
'require poll';
'require tools.mihomo as mihomo'
return view.extend({
load: function () {
return Promise.all([
uci.load('mihomo'),
mihomo.getAppLog(),
mihomo.getCoreLog()
]);
},
render: function (data) {
const appLog = data[1];
const coreLog = data[2];
let m, s, o;
m = new form.Map('mihomo');
s = m.section(form.NamedSection, '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 () {
m.lookupOption('mihomo.log._app_log')[0].getUIElement('log').setValue('');
return mihomo.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(mihomo.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 () {
const element = m.lookupOption('mihomo.log._app_log')[0].getUIElement('log').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 () {
m.lookupOption('mihomo.log._core_log')[0].getUIElement('log').setValue('');
return mihomo.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(mihomo.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 () {
const element = m.lookupOption('mihomo.log._core_log')[0].getUIElement('log').node.firstChild;
element.scrollTop = element.scrollHeight;
};
return m.render();
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -0,0 +1,416 @@
msgid "MihomoTProxy"
msgstr "MihomoTProxy"
msgid "Transparent Proxy with Mihomo on OpenWrt."
msgstr "在 OpenWrt 上使用 Mihomo 进行透明代理。"
msgid "How To Use"
msgstr "使用说明"
msgid "Config"
msgstr "配置"
msgid "Status"
msgstr "状态"
msgid "App Version"
msgstr "插件版本"
msgid "Core Version"
msgstr "核心版本"
msgid "Core Status"
msgstr "核心状态"
msgid "Running"
msgstr "运行中"
msgid "Not Running"
msgstr "未在运行"
msgid "Reload Service"
msgstr "重载服务"
msgid "Restart Service"
msgstr "重启服务"
msgid "Update Dashboard"
msgstr "更新面板"
msgid "Open Dashboard"
msgstr "打开面板"
msgid "Basic Config"
msgstr "基础配置"
msgid "Enable"
msgstr "启用"
msgid "Disable"
msgstr "禁用"
msgid "Auto"
msgstr "自动"
msgid "Mode"
msgstr "模式"
msgid "Type"
msgstr "类型"
msgid "Value"
msgstr "值"
msgid "Scheduled Restart"
msgstr "定时重启"
msgid "Cron Expression"
msgstr "Cron 表达式"
msgid "Choose Profile"
msgstr "选择配置文件"
msgid "File:"
msgstr "文件:"
msgid "Subscription:"
msgstr "订阅:"
msgid "Upload Profile"
msgstr "上传配置文件"
msgid "Mixin"
msgstr "混入"
msgid "Test Profile"
msgstr "检查配置文件"
msgid "Fast Reload"
msgstr "快速重载"
msgid "Proxy Config"
msgstr "代理配置"
msgid "Transparent Proxy"
msgstr "透明代理"
msgid "TCP Proxy Mode"
msgstr "TCP 代理模式"
msgid "UDP Proxy Mode"
msgstr "UDP 代理模式"
msgid "Redirect Mode"
msgstr "Redirect 模式"
msgid "TPROXY Mode"
msgstr "TPROXY 模式"
msgid "TUN Mode"
msgstr "TUN 模式"
msgid "IPv4 DNS Hijack"
msgstr "IPv4 DNS 劫持"
msgid "IPv6 DNS Hijack"
msgstr "IPv6 DNS 劫持"
msgid "IPv4 Proxy"
msgstr "IPv4 代理"
msgid "IPv6 Proxy"
msgstr "IPv6 代理"
msgid "Router Proxy"
msgstr "路由器代理"
msgid "Lan Proxy"
msgstr "局域网代理"
msgid "Access Control"
msgstr "访问控制"
msgid "All Mode"
msgstr "全部模式"
msgid "Allow Mode"
msgstr "白名单模式"
msgid "Block Mode"
msgstr "黑名单模式"
msgid "Bypass"
msgstr "绕过"
msgid "Bypass China Mainland IP"
msgstr "绕过中国大陆 IP"
msgid "Destination TCP Port to Proxy"
msgstr "要代理的 TCP 目标端口"
msgid "Destination UDP Port to Proxy"
msgstr "要代理的 UDP 目标端口"
msgid "All Port"
msgstr "全部端口"
msgid "Commonly Used Port"
msgstr "常用端口"
msgid "Subscription Config"
msgstr "订阅配置"
msgid "Subscription Name"
msgstr "订阅名称"
msgid "Subscription Url"
msgstr "订阅链接"
msgid "User Agent"
msgstr "用户代理UA"
msgid "Mixin Config"
msgstr "混入配置"
msgid "General Config"
msgstr "全局配置"
msgid "Proxy Mode"
msgstr "代理模式"
msgid "Global Mode"
msgstr "全局模式"
msgid "Rule Mode"
msgstr "规则模式"
msgid "Direct Mode"
msgstr "直连模式"
msgid "Match Process"
msgstr "匹配进程"
msgid "Outbound Interface"
msgstr "出站接口"
msgid "TCP Keep Alive Idle"
msgstr "TCP Keep Alive 空闲"
msgid "TCP Keep Alive Interval"
msgstr "TCP Keep Alive 间隔"
msgid "Log Level"
msgstr "日志级别"
msgid "External Control Config"
msgstr "外部控制配置"
msgid "UI Name"
msgstr "UI 名称"
msgid "UI Url"
msgstr "UI 下载地址"
msgid "Service is not running."
msgstr "服务未在运行。"
msgid "API Port"
msgstr "API 端口"
msgid "API Secret"
msgstr "API 密钥"
msgid "Save Proxy Selection"
msgstr "保存节点/策略组选择"
msgid "Inbound Config"
msgstr "入站配置"
msgid "Allow Lan"
msgstr "允许局域网访问"
msgid "HTTP Port"
msgstr "HTTP 端口"
msgid "SOCKS Port"
msgstr "SOCKS 端口"
msgid "Mixed Port"
msgstr "混合端口"
msgid "Redirect Port"
msgstr "Redirect 端口"
msgid "TPROXY Port"
msgstr "TPROXY 端口"
msgid "Authentication"
msgid "身份验证"
msgid "Edit Authentications"
msgstr "编辑身份验证"
msgid "username"
msgstr "用户名"
msgid "password"
msgstr "密码"
msgid "TUN Config"
msgstr "TUN 配置"
msgid "Stack"
msgstr "栈"
msgid "Device"
msgstr "设备"
msgid "MTU"
msgstr "最大传输单元"
msgid "GSO"
msgstr "通用分段卸载"
msgid "GSO Max Size"
msgstr "分段最大长度"
msgid "Endpoint Independent NAT"
msgstr "独立于端点的 NAT"
msgid "DNS Config"
msgstr "DNS 配置"
msgid "DNS Port"
msgstr "DNS 端口"
msgid "DNS Mode"
msgstr "DNS 模式"
msgid "Fake-IP Range"
msgstr "Fake-IP 范围"
msgid "Overwrite Fake-IP Filter"
msgstr "覆盖 Fake-IP 过滤列表"
msgid "Edit Fake-IP Filters"
msgstr "编辑 Fake-IP 过滤列表"
msgid "Fake-IP Filter Mode"
msgstr "Fake-IP 过滤模式"
msgid "Fake-IP Cache"
msgstr "Fake-IP 缓存"
msgid "Respect Rules"
msgstr "遵循分流规则"
msgid "Use System Hosts"
msgstr "使用系统的 Hosts"
msgid "Use Hosts"
msgstr "使用 Hosts"
msgid "Overwrite Hosts"
msgstr "覆盖 Hosts"
msgid "Edit Hosts"
msgstr "编辑 Hosts"
msgid "Domain Name"
msgstr "域名"
msgid "Overwrite Nameserver"
msgstr "覆盖 DNS 服务器"
msgid "Edit Nameservers"
msgstr "编辑 DNS 服务器"
msgid "Nameserver"
msgstr "DNS 服务器"
msgid "Overwrite Fallback Filter"
msgstr "覆盖 Fallback 过滤列表"
msgid "Edit Fallback Filters"
msgstr "编辑 Fallback 过滤列表"
msgid "Overwrite Nameserver Policy"
msgstr "覆盖 DNS 服务器查询策略"
msgid "Edit Nameserver Policies"
msgstr "编辑 DNS 服务器查询策略"
msgid "Matcher"
msgstr "匹配"
msgid "GeoX Config"
msgstr "GeoX 配置"
msgid "GeoIP Format"
msgstr "GeoIP 格式"
msgid "GeoData Loader"
msgstr "GeoData 加载器"
msgid "Standard Loader"
msgstr "标准加载器"
msgid "Memory Conservative Loader"
msgstr "为内存受限设备优化的加载器"
msgid "GeoSite Url"
msgstr "GeoSite 下载地址"
msgid "GeoIP(MMDB) Url"
msgstr "GeoIP(MMDB) 下载地址"
msgid "GeoIP(DAT) Url"
msgstr "GeoIP(DAT) 下载地址"
msgid "GeoIP(ASN) Url"
msgstr "GeoIP(ASN) 下载地址"
msgid "GeoX Auto Update"
msgstr "定时更新GeoX文件"
msgid "GeoX Update Interval"
msgstr "GeoX 文件更新间隔"
msgid "Hour"
msgstr "小时"
msgid "Mixin File Content"
msgstr "混入文件内容"
msgid "Please go to the editor tab to edit the file for mixin"
msgstr "请前往编辑器标签编辑用于混入的文件"
msgid "Editor"
msgstr "编辑器"
msgid "File for Mixin"
msgstr "用于混入的文件"
msgid "Profile for Startup"
msgstr "用于启动的配置文件"
msgid "File for Reserved IP"
msgstr "IPv4 保留地址"
msgid "File for Reserved IP6"
msgstr "IPv6 保留地址"
msgid "Log"
msgstr "日志"
msgid "App Log"
msgstr "应用日志"
msgid "Core Log"
msgstr "核心日志"
msgid "Clear Log"
msgstr "清空日志"
msgid "Scroll To Bottom"
msgstr "滚动到底部"

View File

@ -0,0 +1,46 @@
#!/bin/sh
. $IPKG_INSTROOT/etc/mihomo/scripts/constants.sh
action=$1
shift
case "$action" in
clear)
case "$1" in
app_log)
echo -n > "$RUN_APP_LOG_PATH"
;;
core_log)
echo -n > "$RUN_CORE_LOG_PATH"
;;
esac
;;
load)
case "$1" in
profile)
yq -M -o json < "$RUN_PROFILE_PATH"
;;
esac
;;
service)
case "$1" in
reload)
/etc/init.d/mihomo reload
;;
restart)
/etc/init.d/mihomo restart
;;
esac
;;
version)
case "$1" in
app)
opkg list-installed | grep "luci-app-mihomo" | cut -d " " -f 3
;;
core)
mihomo -v | grep "Mihomo" | cut -d " " -f 3
;;
esac
;;
esac

View File

@ -0,0 +1,37 @@
{
"admin/services/mihomo": {
"title": "MihomoTProxy",
"action": {
"type": "alias",
"path": "admin/services/mihomo/config"
},
"depends": {
"acl": [ "luci-app-mihomo" ],
"uci": { "mihomo": true }
}
},
"admin/services/mihomo/config": {
"title": "Config",
"order": 10,
"action": {
"type": "view",
"path": "mihomo/config"
}
},
"admin/services/mihomo/editor": {
"title": "Editor",
"order": 20,
"action": {
"type": "view",
"path": "mihomo/editor"
}
},
"admin/services/mihomo/log": {
"title": "Log",
"order": 30,
"action": {
"type": "view",
"path": "mihomo/log"
}
}
}

View File

@ -0,0 +1,32 @@
{
"luci-app-mihomo": {
"description": "Grant access to mihomo procedures",
"read": {
"uci": [ "mihomo" ],
"ubus": {
"service": [ "list" ]
},
"file": {
"/etc/mihomo/profiles/*.yaml": ["read"],
"/etc/mihomo/profiles/*.yml": ["read"],
"/etc/mihomo/mixin.yaml": ["read"],
"/etc/mihomo/run/config.yaml": ["read"],
"/etc/mihomo/nftables/reserved_ip.nft": ["read"],
"/etc/mihomo/nftables/reserved_ip6.nft": ["read"],
"/etc/mihomo/run/*.log": ["read"],
"/usr/libexec/mihomo-call": ["exec"]
}
},
"write": {
"uci": [ "mihomo" ],
"file": {
"/etc/mihomo/profiles/*.yaml": ["write"],
"/etc/mihomo/profiles/*.yml": ["write"],
"/etc/mihomo/mixin.yaml": ["write"],
"/etc/mihomo/run/config.yaml": ["write"],
"/etc/mihomo/nftables/reserved_ip.nft": ["write"],
"/etc/mihomo/nftables/reserved_ip6.nft": ["write"]
}
}
}
}