mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-15 04:44:47 +08:00
579 lines
23 KiB
TypeScript
579 lines
23 KiB
TypeScript
import { CORE_TYPE, LuciFlied, PROXY_TYPE, normalizeCoreType, ShuntTag } from "../../../enum/hijpass";
|
|
import uci from "uci";
|
|
import ui from "ui";
|
|
import { NotificationUtils } from "../../base/luci/notification";
|
|
import { FactoryType } from "../../../core/adapter";
|
|
import { ShuntUtils } from "../../feature/shunt/section";
|
|
import { MAX_PROXY_CHAIN_NODES, ProxyChainUtils } from "../../feature/proxy/chain";
|
|
import { FirewallDnsForwardUtils } from "../../feature/firewall/dns-forward";
|
|
|
|
const XRAY_ROUTE_PROTOCOLS = ['http', 'tls', 'quic', 'bittorrent'];
|
|
const XRAY_TLS_PROTOCOLS = ['hysteria2', 'vless'];
|
|
|
|
function normalizeList(value: any): string[] {
|
|
if (!value) {
|
|
return [];
|
|
}
|
|
return Array.isArray(value) ? value.filter((item: string) => item) : [value];
|
|
}
|
|
|
|
function isSpecialProxyNode(node: string) {
|
|
return node === ShuntTag.DIRECT_OUTBOUND_TAG
|
|
|| node === ShuntTag.BLOCK_OUTBOUND_TAG;
|
|
}
|
|
|
|
function reportWarnings(title: string, warnings: string[]): boolean {
|
|
if (warnings.length === 0) {
|
|
return true;
|
|
}
|
|
|
|
NotificationUtils.warning(title, warnings.join('\n'), 8000);
|
|
return false;
|
|
}
|
|
|
|
function isTemplateShuntActive() {
|
|
return uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'enabled') !== '0'
|
|
&& uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'config_type') === 'tmpl';
|
|
}
|
|
|
|
function checkMissingProxyRefs() {
|
|
const existingNodes = new Set<string>();
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.PROXY_NODE_TYPE, function (section: any) {
|
|
if (section.name) {
|
|
existingNodes.add(section.name);
|
|
}
|
|
});
|
|
|
|
const warnings: string[] = [];
|
|
function checkRef(ref: string, message: string) {
|
|
if (ref && !isSpecialProxyNode(ref) && !existingNodes.has(ref)) {
|
|
warnings.push(message);
|
|
}
|
|
}
|
|
|
|
const defaultNode = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'default_proxy_node');
|
|
if (!defaultNode) {
|
|
warnings.push(_('Default global exit is not configured'));
|
|
} else {
|
|
checkRef(defaultNode, _('Default global exit "%s" does not exist').format(defaultNode));
|
|
}
|
|
|
|
const rulesetNode = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'ruleset_out_node');
|
|
checkRef(rulesetNode, _('Rule-set exit "%s" does not exist').format(rulesetNode));
|
|
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.SHUNT_ROUTE_RULE_TYPE, function (section: any) {
|
|
if (section.enabled !== '1') {
|
|
return;
|
|
}
|
|
const ruleName = section.name || section['.name'];
|
|
if (section.ip_version_split === '1') {
|
|
if (!section.proxy_node_v4) {
|
|
warnings.push(_('Route rule "%s" IPv4 proxy node is not configured').format(ruleName));
|
|
} else {
|
|
checkRef(section.proxy_node_v4, _('Route rule "%s" IPv4 proxy node "%s" does not exist').format(ruleName, section.proxy_node_v4));
|
|
}
|
|
if (!section.proxy_node_v6) {
|
|
warnings.push(_('Route rule "%s" IPv6 proxy node is not configured').format(ruleName));
|
|
} else {
|
|
checkRef(section.proxy_node_v6, _('Route rule "%s" IPv6 proxy node "%s" does not exist').format(ruleName, section.proxy_node_v6));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!section.proxy_node) {
|
|
warnings.push(_('Route rule "%s" proxy node is not configured').format(ruleName));
|
|
} else {
|
|
checkRef(section.proxy_node, _('Route rule "%s" proxy node "%s" does not exist').format(ruleName, section.proxy_node));
|
|
}
|
|
});
|
|
|
|
return reportWarnings(_('Configuration contains missing proxy nodes'), warnings);
|
|
}
|
|
|
|
function checkDisabledProxyRefs() {
|
|
const disabledNodes = new Set<string>();
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.PROXY_NODE_TYPE, function (section: any) {
|
|
if (section.enabled !== '1' && section.name) {
|
|
disabledNodes.add(section.name);
|
|
}
|
|
});
|
|
|
|
if (disabledNodes.size === 0) {
|
|
return true;
|
|
}
|
|
|
|
const warnings: string[] = [];
|
|
|
|
const defaultNode = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'default_proxy_node');
|
|
if (defaultNode && disabledNodes.has(defaultNode)) {
|
|
warnings.push(_('Default global exit "%s" is not enabled').format(defaultNode));
|
|
}
|
|
|
|
const rulesetNode = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'ruleset_out_node');
|
|
if (rulesetNode && disabledNodes.has(rulesetNode)) {
|
|
warnings.push(_('Rule-set exit "%s" is not enabled').format(rulesetNode));
|
|
}
|
|
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.SHUNT_ROUTE_RULE_TYPE, function (section: any) {
|
|
if (section.enabled !== '1') {
|
|
return;
|
|
}
|
|
const ruleName = section.name || section['.name'];
|
|
if (section.ip_version_split === '1') {
|
|
if (section.proxy_node_v4 && disabledNodes.has(section.proxy_node_v4)) {
|
|
warnings.push(_('Route rule "%s" IPv4 proxy node "%s" is not enabled').format(ruleName, section.proxy_node_v4));
|
|
}
|
|
if (section.proxy_node_v6 && disabledNodes.has(section.proxy_node_v6)) {
|
|
warnings.push(_('Route rule "%s" IPv6 proxy node "%s" is not enabled').format(ruleName, section.proxy_node_v6));
|
|
}
|
|
} else if (section.proxy_node && disabledNodes.has(section.proxy_node)) {
|
|
warnings.push(_('Route rule "%s" proxy node "%s" is not enabled').format(ruleName, section.proxy_node));
|
|
}
|
|
if (section.dns_proxy_node && disabledNodes.has(section.dns_proxy_node)) {
|
|
warnings.push(_('Route rule "%s" DNS exit node "%s" is not enabled').format(ruleName, section.dns_proxy_node));
|
|
}
|
|
});
|
|
|
|
return reportWarnings(_('Configuration contains disabled nodes'), warnings);
|
|
}
|
|
|
|
function checkLoadBalanceRefs() {
|
|
const sections = ProxyChainUtils.getProxySectionMap();
|
|
const warnings: string[] = [];
|
|
|
|
sections.forEach((section: any) => {
|
|
if (section.enabled !== '1' || section.type !== PROXY_TYPE.LOAD_BALANCE) return;
|
|
|
|
const nodeName = ProxyChainUtils.getProxyName(section);
|
|
const core = normalizeCoreType(section.core);
|
|
if (core !== CORE_TYPE.SING_BOX && core !== CORE_TYPE.XRAY) {
|
|
warnings.push(_('Load balancing node "%s" has an unsupported backend core').format(nodeName));
|
|
}
|
|
|
|
const members = ProxyChainUtils.normalizeProxyNodeList(section.member_node);
|
|
if (members.length === 0) {
|
|
warnings.push(_('Load balancing node "%s" has no member nodes').format(nodeName));
|
|
return;
|
|
}
|
|
|
|
const duplicateMembers = members.filter((member, index) => members.indexOf(member) !== index);
|
|
if (duplicateMembers.length > 0) {
|
|
warnings.push(_('Load balancing node "%s" contains duplicate member nodes: %s')
|
|
.format(nodeName, Array.from(new Set(duplicateMembers)).join(', ')));
|
|
}
|
|
|
|
members.forEach((memberName) => {
|
|
if (memberName === nodeName) {
|
|
warnings.push(_('Load balancing node "%s" cannot include itself').format(nodeName));
|
|
return;
|
|
}
|
|
|
|
const member = sections.get(memberName);
|
|
if (!member) {
|
|
warnings.push(_('Load balancing node "%s" member node "%s" does not exist')
|
|
.format(nodeName, memberName));
|
|
return;
|
|
}
|
|
if (member.type === PROXY_TYPE.LOAD_BALANCE) {
|
|
warnings.push(_('Load balancing node "%s" cannot contain load balancing member "%s"')
|
|
.format(nodeName, memberName));
|
|
}
|
|
if (member.enabled !== '1') {
|
|
warnings.push(_('Load balancing node "%s" member node "%s" is not enabled')
|
|
.format(nodeName, memberName));
|
|
}
|
|
if (!ProxyChainUtils.isPort(member.socks_port)) {
|
|
warnings.push(_('Load balancing node "%s" member node "%s" has no valid SOCKS port configured')
|
|
.format(nodeName, memberName));
|
|
}
|
|
});
|
|
|
|
if (section.fallback_tag && members.indexOf(section.fallback_tag) < 0) {
|
|
warnings.push(_('Load balancing node "%s" fallback node "%s" is not a member')
|
|
.format(nodeName, section.fallback_tag));
|
|
}
|
|
});
|
|
|
|
return reportWarnings(_('Configuration contains invalid load balancing nodes'), warnings);
|
|
}
|
|
|
|
function checkProxyChainRefs() {
|
|
const sections = ProxyChainUtils.getProxySectionMap();
|
|
const warnings: string[] = [];
|
|
|
|
sections.forEach((section: any) => {
|
|
if (section.enabled !== '1'
|
|
|| section.type === PROXY_TYPE.LOAD_BALANCE
|
|
|| !section.upstream_proxy_node) {
|
|
return;
|
|
}
|
|
|
|
const nodeName = ProxyChainUtils.getProxyName(section);
|
|
const upstreamName = section.upstream_proxy_node;
|
|
const upstreamSection = sections.get(upstreamName);
|
|
|
|
if (!upstreamSection) {
|
|
warnings.push(_('Proxy node "%s" upstream proxy node "%s" does not exist').format(nodeName, upstreamName));
|
|
return;
|
|
}
|
|
if (upstreamSection.enabled !== '1') {
|
|
warnings.push(_('Proxy node "%s" upstream proxy node "%s" is not enabled').format(nodeName, upstreamName));
|
|
}
|
|
if (!ProxyChainUtils.isPort(upstreamSection.socks_port)) {
|
|
warnings.push(_('Proxy node "%s" upstream proxy node "%s" has no valid SOCKS port configured').format(nodeName, upstreamName));
|
|
}
|
|
|
|
const result = ProxyChainUtils.collectProxyChain(nodeName, sections);
|
|
if (!result.cycle && result.chain.length > MAX_PROXY_CHAIN_NODES) {
|
|
warnings.push(_('Proxy node "%s" nesting chain exceeds %d nodes: %s')
|
|
.format(nodeName, MAX_PROXY_CHAIN_NODES, result.chain.join(' -> ')));
|
|
}
|
|
});
|
|
|
|
const dependencyCycle = ProxyChainUtils.findProxyDependencyCycle(sections);
|
|
if (dependencyCycle) {
|
|
warnings.push(_('Proxy node dependency chain has a cycle: %s').format(dependencyCycle.join(' -> ')));
|
|
}
|
|
|
|
return reportWarnings(_('Configuration contains invalid upstream proxy nodes'), warnings);
|
|
}
|
|
|
|
function checkInvalidDnsNodeRefs() {
|
|
const existingDnsNodes = new Set<string>();
|
|
const disabledDnsNodes = new Set<string>();
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.SHUNT_DNS_NODE_TYPE, function (section: any) {
|
|
if (!section.tag) {
|
|
return;
|
|
}
|
|
existingDnsNodes.add(section.tag);
|
|
if (section.enabled === '0') {
|
|
disabledDnsNodes.add(section.tag);
|
|
}
|
|
});
|
|
|
|
const missingWarnings: string[] = [];
|
|
const disabledWarnings: string[] = [];
|
|
function checkRef(ref: string, label: string) {
|
|
if (!ref) {
|
|
return;
|
|
}
|
|
if (!existingDnsNodes.has(ref)) {
|
|
missingWarnings.push(_('%s "%s" does not exist').format(label, ref));
|
|
return;
|
|
}
|
|
if (disabledDnsNodes.has(ref)) {
|
|
disabledWarnings.push(_('%s "%s" is not enabled').format(label, ref));
|
|
}
|
|
}
|
|
|
|
const defaultDnsNode = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'default_dns_node');
|
|
if (!defaultDnsNode) {
|
|
missingWarnings.push(_('Default global DNS is not configured'));
|
|
} else {
|
|
checkRef(defaultDnsNode, _('Default global DNS'));
|
|
}
|
|
checkRef(
|
|
uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'ruleset_dns_node'),
|
|
_('Rule-set DNS')
|
|
);
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.SHUNT_ROUTE_RULE_TYPE, function (section: any) {
|
|
if (section.enabled !== '1') {
|
|
return;
|
|
}
|
|
checkRef(section.dns_node, _('Route rule "%s" DNS node').format(section.name || section['.name']));
|
|
});
|
|
|
|
return reportWarnings(_('Configuration contains missing DNS nodes'), missingWarnings)
|
|
&& reportWarnings(_('Configuration contains disabled DNS nodes'), disabledWarnings);
|
|
}
|
|
|
|
function checkMissingDnsExitRefs() {
|
|
const existingNodeNames = new Set<string>();
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.PROXY_NODE_TYPE, function (section: any) {
|
|
if (section.name) existingNodeNames.add(section.name);
|
|
});
|
|
|
|
const shuntType = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'type');
|
|
const rulesetConvert = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'ruleset_convert');
|
|
const missingDnsExits: string[] = [];
|
|
ShuntUtils.getRouteSection().rules.forEach(function (section: any) {
|
|
if (section.enabled !== '1') {
|
|
return;
|
|
}
|
|
const dnsExit = section.dnsProxyNode;
|
|
const ruleName = section.name || _('Unnamed Rule');
|
|
if (routeRuleRequiresDnsExit(section, shuntType, rulesetConvert) && !dnsExit) {
|
|
missingDnsExits.push(_('Route rule "%s" uses IPv4/IPv6 split and must specify a DNS exit node').format(ruleName));
|
|
return;
|
|
}
|
|
if (dnsExit && dnsExit !== ShuntTag.DIRECT_OUTBOUND_TAG && !existingNodeNames.has(dnsExit)) {
|
|
missingDnsExits.push(_('Route rule "%s" DNS exit node "%s" does not exist').format(ruleName, dnsExit));
|
|
}
|
|
});
|
|
|
|
return reportWarnings(_('Configuration contains invalid DNS exit nodes'), missingDnsExits);
|
|
}
|
|
|
|
function routeRuleRequiresDnsExit(rule: any, shuntType: string, rulesetConvert: string) {
|
|
if (rule.ipVersionSplit !== '1') {
|
|
return false;
|
|
}
|
|
|
|
const ruleDTO = ShuntUtils.parseRuleList(rule);
|
|
if (shuntType === FactoryType.XRAY) {
|
|
return ruleDTO.containDomain() || ruleDTO.geoSite.length > 0;
|
|
}
|
|
|
|
return ruleDTO.containDomain()
|
|
|| ruleDTO.containRuleSet()
|
|
|| (rulesetConvert === '1' && ruleDTO.geoSite.length > 0);
|
|
}
|
|
|
|
function checkFirewallPortTargets() {
|
|
const enabledProxyPorts = new Set<string>();
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.PROXY_NODE_TYPE, function (section: any) {
|
|
if (section.enabled === '1' && section.listen_port) {
|
|
enabledProxyPorts.add(section.listen_port);
|
|
}
|
|
});
|
|
|
|
const proxyPort = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.FIREWALL_SECTION_TYPE, 'proxy_port');
|
|
const shuntPort = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.FIREWALL_SECTION_TYPE, 'shunt_port');
|
|
const shuntEnabled = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'enabled') !== '0';
|
|
const shuntListenPort = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'shunt_listen_port');
|
|
const warnings: string[] = [];
|
|
|
|
if (proxyPort && !enabledProxyPorts.has(proxyPort)) {
|
|
warnings.push(_('Firewall proxy port %s does not reference an enabled proxy node').format(proxyPort));
|
|
}
|
|
if (shuntPort
|
|
&& !enabledProxyPorts.has(shuntPort)
|
|
&& !(shuntEnabled && shuntListenPort === shuntPort)) {
|
|
warnings.push(_('Firewall shunt port %s does not reference an enabled proxy node or shunt service').format(shuntPort));
|
|
}
|
|
|
|
return reportWarnings(_('Configuration conflict'), warnings);
|
|
}
|
|
|
|
function checkFirewallDnsForwardTarget() {
|
|
const error = FirewallDnsForwardUtils.validateSource(FirewallDnsForwardUtils.getSource());
|
|
return error === true || reportWarnings(_('Configuration conflict'), [error]);
|
|
}
|
|
|
|
function checkDnsRoutingTargets() {
|
|
const dnsService = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.DNS_SECTION_TYPE, 'dns_service');
|
|
if (dnsService !== 'chinadns-ng') {
|
|
return true;
|
|
}
|
|
|
|
const routingTarget = FirewallDnsForwardUtils.getRoutingTarget();
|
|
const routingServer = routingTarget.port ? '127.0.0.1#' + routingTarget.port : '';
|
|
const warnings: string[] = [];
|
|
[
|
|
['direct_dns', _('Direct Domain DNS')],
|
|
['proxy_dns', _('Proxy Domain DNS')],
|
|
].forEach(([option, label]) => {
|
|
const value = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.DNS_SECTION_TYPE, option);
|
|
if (!value || value === 'dnsmasq' || value === 'system' || value === 'custom') {
|
|
return;
|
|
}
|
|
if (!routingTarget.available || value !== routingServer) {
|
|
warnings.push(_('%s references an unavailable Core DNS port').format(label));
|
|
}
|
|
});
|
|
|
|
return reportWarnings(_('Configuration conflict'), warnings);
|
|
}
|
|
|
|
function checkXrayRouteProtocols() {
|
|
const shuntType = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'type');
|
|
if (shuntType !== FactoryType.XRAY) {
|
|
return true;
|
|
}
|
|
|
|
const warnings: string[] = [];
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.SHUNT_ROUTE_RULE_TYPE, function (section: any) {
|
|
if (section.enabled !== '1') {
|
|
return;
|
|
}
|
|
|
|
const unsupported = normalizeList(section.protocol)
|
|
.filter((protocol) => XRAY_ROUTE_PROTOCOLS.indexOf(protocol) === -1);
|
|
if (unsupported.length > 0) {
|
|
warnings.push(_('Route rule "%s" contains protocols unsupported by Xray: %s')
|
|
.format(section.name || section['.name'], unsupported.join(', ')));
|
|
}
|
|
});
|
|
|
|
if (warnings.length > 0) {
|
|
NotificationUtils.warning(
|
|
_('Unsupported Xray protocol configuration'),
|
|
warnings.join('\n') + '\n' + _('Xray route protocol only supports: %s').format(XRAY_ROUTE_PROTOCOLS.join(', ')),
|
|
8000
|
|
);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function checkXrayDnsServerTypes() {
|
|
const shuntType = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'type');
|
|
if (shuntType !== FactoryType.XRAY) {
|
|
return true;
|
|
}
|
|
|
|
const warnings: string[] = [];
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.SHUNT_DNS_NODE_TYPE, function (section: any) {
|
|
if (section.enabled === '0') {
|
|
return;
|
|
}
|
|
if (section.type === 'tls') {
|
|
warnings.push(_('DNS node "%s" uses TLS, which is only supported by sing-box').format(section.tag || section['.name']));
|
|
}
|
|
});
|
|
|
|
return reportWarnings(_('Unsupported Xray DNS configuration'), warnings);
|
|
}
|
|
|
|
function checkXrayTlsInsecure() {
|
|
const warnings: string[] = [];
|
|
uci.sections(LuciFlied.CONF_NAME, LuciFlied.PROXY_NODE_TYPE, function (section: any) {
|
|
if (section.enabled !== '1' || normalizeCoreType(section.core) !== FactoryType.XRAY || section.tls_insecure !== '1') {
|
|
return;
|
|
}
|
|
if (XRAY_TLS_PROTOCOLS.indexOf(section.type) !== -1) {
|
|
warnings.push(_('Proxy node "%s" enables insecure TLS, which is not supported by current Xray').format(section.name || section['.name']));
|
|
}
|
|
});
|
|
|
|
return reportWarnings(_('Unsupported Xray TLS configuration'), warnings);
|
|
}
|
|
|
|
function confirmWarning(title: string, description: string, warnings: string[]) {
|
|
return new Promise<boolean>((resolve) => {
|
|
ui.showModal(title, [
|
|
E('div', {
|
|
style: [
|
|
'box-sizing:border-box',
|
|
'width:100%',
|
|
'margin:0 0 14px 0',
|
|
'padding:14px 16px',
|
|
'border-radius:4px',
|
|
'background:rgba(127,127,127,0.08)',
|
|
'line-height:1.55'
|
|
].join(';')
|
|
}, [
|
|
E('div', {style: 'margin:0 0 12px 0'}, description),
|
|
E('div', {style: 'display:flex;flex-direction:column;gap:6px;margin:0'}, warnings.map((warning) => (
|
|
E('div', {style: 'margin:0;word-break:break-word'}, warning)
|
|
)))
|
|
]),
|
|
E('div', {style: 'display:flex;justify-content:flex-end;gap:10px;margin:0'}, [
|
|
E('button', {
|
|
class: 'btn cbi-button',
|
|
click: function (ev: Event) {
|
|
ev.preventDefault();
|
|
ui.hideModal();
|
|
resolve(false);
|
|
}
|
|
}, _('Cancel')),
|
|
E('button', {
|
|
class: 'btn cbi-button-action',
|
|
click: function (ev: Event) {
|
|
ev.preventDefault();
|
|
ui.hideModal();
|
|
resolve(true);
|
|
}
|
|
}, _('Continue Apply'))
|
|
])
|
|
]);
|
|
});
|
|
}
|
|
|
|
function getSkippedRouteRuleMessage(rule: any, shuntType: string, rulesetConvert: string) {
|
|
const ruleDTO = ShuntUtils.parseRuleList(rule);
|
|
const ruleName = rule.name || rule['.name'] || _('Unnamed Rule');
|
|
const canConvertGeoRule = rulesetConvert === '1';
|
|
|
|
if (shuntType === FactoryType.XRAY) {
|
|
if (ruleDTO.isEmpty('geo')) {
|
|
return _('Route rule "%s" has no valid match conditions for Xray').format(ruleName);
|
|
}
|
|
if (ruleDTO.containRuleSet()
|
|
&& !ruleDTO.containDomain()
|
|
&& !ruleDTO.containIp()
|
|
&& !ruleDTO.containGeo()) {
|
|
return _('Route rule "%s" only contains rule-set target conditions unsupported by Xray').format(ruleName);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
if (shuntType === FactoryType.SING_BOX) {
|
|
if (!canConvertGeoRule && ruleDTO.containGeo()) {
|
|
ruleDTO.geoSite = [];
|
|
ruleDTO.geoIp = [];
|
|
if (!ruleDTO.containDomain() && !ruleDTO.containIp() && !ruleDTO.containRuleSet()) {
|
|
return _('Route rule "%s" has no target conditions for sing-box after removing unconverted geosite/geoip').format(ruleName);
|
|
}
|
|
}
|
|
if (ruleDTO.isEmpty('set') && !(canConvertGeoRule && ruleDTO.containGeo())) {
|
|
return _('Route rule "%s" has no valid match conditions for sing-box').format(ruleName);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function confirmSkippedRouteRules() {
|
|
const shuntType = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'type');
|
|
const rulesetConvert = uci.get_first(LuciFlied.CONF_NAME, LuciFlied.SHUNT_SECTION_TYPE, 'ruleset_convert');
|
|
const warnings: string[] = [];
|
|
|
|
ShuntUtils.getRouteSection().rules.forEach((rule) => {
|
|
if (rule.enabled !== '1') {
|
|
return;
|
|
}
|
|
|
|
const message = getSkippedRouteRuleMessage(rule, shuntType, rulesetConvert);
|
|
if (message) warnings.push(message);
|
|
});
|
|
|
|
if (warnings.length > 0) {
|
|
return confirmWarning(
|
|
_('Some route rules will be skipped'),
|
|
_('The following rules have no valid configuration for the current core and options, so they will not be written to the final configuration. Continue applying the configuration?'),
|
|
warnings
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const PrecheckUtils = {
|
|
runSaveApplyPrechecks: async function () {
|
|
if (!checkProxyChainRefs()
|
|
|| !checkLoadBalanceRefs()
|
|
|| !checkXrayTlsInsecure()
|
|
|| !checkFirewallPortTargets()
|
|
|| !checkFirewallDnsForwardTarget()
|
|
|| !checkDnsRoutingTargets()) {
|
|
return false;
|
|
}
|
|
|
|
if (!isTemplateShuntActive()) {
|
|
return true;
|
|
}
|
|
|
|
return checkMissingProxyRefs()
|
|
&& checkDisabledProxyRefs()
|
|
&& checkInvalidDnsNodeRefs()
|
|
&& checkMissingDnsExitRefs()
|
|
&& await confirmSkippedRouteRules()
|
|
&& checkXrayRouteProtocols()
|
|
&& checkXrayDnsServerTypes();
|
|
},
|
|
}
|
|
|
|
export { PrecheckUtils }
|