diff --git a/web/assets/js/util/common.js b/web/assets/js/util/common.js index fb58da96..f411148b 100644 --- a/web/assets/js/util/common.js +++ b/web/assets/js/util/common.js @@ -104,4 +104,13 @@ function usageColor(data, threshold, total) { default: return 'red'; } -} \ No newline at end of file +} + +function areAllItemsExist(array1, array2) { + for (let i = 0; i < array1.length; i++) { + if (!array2.includes(array1[i])) { + return false; + } + } + return true; + } \ No newline at end of file diff --git a/web/html/xui/component/setting.html b/web/html/xui/component/setting.html index 00eeb259..741c7d9f 100644 --- a/web/html/xui/component/setting.html +++ b/web/html/xui/component/setting.html @@ -1,6 +1,12 @@ {{define "component/settingListItem"}} - + + + + + + + @@ -9,10 +15,7 @@ - - @@ -80,45 +96,159 @@ style="max-width: 300px"> - {{ i18n "confirm" }} - - - - - {{ i18n "pages.setting.advancedTemplate"}} - - - - - - - - - - - - {{ i18n "pages.setting.completeTemplate"}} - - {{ i18n "pages.setting.resetDefaultConfig" }} - - - - - - - - - - - - - - + + + +

+ + {{ i18n "pages.settings.infoDesc" }} +

+
+ + + + + +

+ + {{ i18n "pages.settings.templates.generalConfigsDesc" }} +

+
+ + + + + + + + + + + + + + + + + + + +
+ + +

+ + {{ i18n "pages.settings.templates.blockConfigsDesc" }} +

+
+ + + + +
+ + +

+ + {{ i18n "pages.settings.templates.countryConfigsDesc" }} +

+
+ + + + + + + + + + + + +
+ + +

+ + {{ i18n "pages.settings.templates.ipv4ConfigsDesc" }} +

+
+ + +
+ + +

+ + {{ i18n "pages.settings.templates.manualListsDesc" }} +

+
+ + + + +
+
+
+ + + + + + + + + + + + + + + + {{ i18n "pages.settings.resetDefaultConfig" }} + + + +
+
+
+ + +

+ + {{ i18n "pages.settings.infoDesc" }} +

+
+ + + + + + +
@@ -143,11 +273,73 @@ allSetting: new AllSetting(), saveBtnDisable: true, user: {}, - lang : getLang() + lang : getLang(), + ipv4Settings: { + tag: "IPv4", + protocol: "freedom", + settings: { + domainStrategy: "UseIPv4" + } + }, + directSettings: { + tag: "direct", + protocol: "freedom" + }, + outboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"], + routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"], + settingsData: { + protocols: { + bittorrent: ["bittorrent"], + }, + ips: { + local: ["geoip:private"], + google: ["geoip:google"], + cn: ["geoip:cn"], + ir: ["geoip:ir"], + ru: ["geoip:ru"], + }, + domains: { + ads: [ + "geosite:category-ads-all", + "geosite:category-ads", + "geosite:google-ads", + "geosite:spotify-ads" + ], + porn: ["geosite:category-porn"], + openai: ["geosite:openai"], + google: ["geosite:google"], + spotify: ["geosite:spotify"], + netflix: ["geosite:netflix"], + cn: [ + "geosite:cn", + "regexp:.*\\.cn$" + ], + ru: [ + "geosite:category-gov-ru", + "regexp:.*\\.ru$" + ], + ir: [ + "regexp:.*\\.ir$", + "ext:iran.dat:ir", + "ext:iran.dat:other", + "ext:iran.dat:ads", + "geosite:category-ir" + ] + }, + familyProtectDNS: { + "servers": [ + "1.1.1.2", + "1.0.0.2", + "94.140.14.14", + "94.140.15.15" + ], + "queryStrategy": "UseIPv4" + }, + } }, methods: { - loading(spinning = true) { - this.spinning = spinning; + loading(spinning = true, obj) { + if (obj == null) this.spinning = spinning; }, async getAllSetting() { this.loading(true); @@ -204,6 +396,75 @@ this.saveBtnDisable = true; } }, + syncRulesWithOutbound(tag, setting) { + const newTemplateSettings = this.templateSettings; + const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag); + const outboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.tag === tag); + if (!haveRules && outboundIndex > 0){ + newTemplateSettings.outbounds.splice(outboundIndex); + } + if (haveRules && outboundIndex < 0) { + + newTemplateSettings.outbounds.push(setting); + } + this.templateSettings = newTemplateSettings; + }, + templateRuleGetter(routeSettings) { + const { property, outboundTag } = routeSettings; + let result = []; + if (this.templateSettings != null) { + this.templateSettings.routing.rules.forEach( + (routingRule) => { + if ( + routingRule.hasOwnProperty(property) && + routingRule.hasOwnProperty("outboundTag") && + routingRule.outboundTag === outboundTag + ) { + result.push(...routingRule[property]); + } + } + ); + } + return result; + }, + templateRuleSetter(routeSettings) { + const { data, property, outboundTag } = routeSettings; + const oldTemplateSettings = this.templateSettings; + const newTemplateSettings = oldTemplateSettings; + currentProperty = this.templateRuleGetter({outboundTag: outboundTag, property: property}) + if (currentProperty.length == 0) { + const propertyRule = { + type: "field", + outboundTag, + [property]: data + }; + newTemplateSettings.routing.rules.push(propertyRule); + } + else { + const newRules = []; + insertedOnce = false; + newTemplateSettings.routing.rules.forEach( + (routingRule) => { + if ( + routingRule.hasOwnProperty(property) && + routingRule.hasOwnProperty("outboundTag") && + routingRule.outboundTag === outboundTag + ) { + if (!insertedOnce && data.length>0){ + insertedOnce = true; + routingRule[property] = data; + newRules.push(routingRule); + } + } + else { + newRules.push(routingRule); + } + } + ); + newTemplateSettings.routing.rules = newRules; + } + this.templateSettings = newTemplateSettings; + } }, async mounted() { await this.getAllSetting(); @@ -214,7 +475,7 @@ }, computed: { templateSettings: { - get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null ; }, + get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null; }, set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2) }, }, inboundSettings: { @@ -241,75 +502,329 @@ this.templateSettings = newTemplateSettings }, }, - torrentSettings: { + freedomStrategy: { get: function () { - torrentFilter = false - if(this.templateSettings != null){ - this.templateSettings.routing.rules.forEach(routingRule => { - if(routingRule.hasOwnProperty("protocol")){ - if (routingRule.protocol[0] === "bittorrent" && routingRule.outboundTag == "blocked"){ - torrentFilter = true - } - } - }); - } - return torrentFilter + if (!this.templateSettings) return "AsIs"; + freedomOutbound = this.templateSettings.outbounds.find((o) => o.tag === "direct"); + if (!freedomOutbound) return "AsIs"; + if (!freedomOutbound.settings || !freedomOutbound.settings.domainStrategy) return "AsIs"; + return freedomOutbound.settings.domainStrategy; }, set: function (newValue) { - newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig); - if (newValue){ - newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"protocol\": [\"bittorrent\"],\"type\": \"field\"}")) + newTemplateSettings = this.templateSettings; + freedomOutboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.protocol === "freedom" && !o.tag); + if (!newTemplateSettings.outbounds[freedomOutboundIndex].settings) { + newTemplateSettings.outbounds[freedomOutboundIndex].settings = {"domainStrategy": newValue}; + } else { + newTemplateSettings.outbounds[freedomOutboundIndex].settings.domainStrategy = newValue; } - else { - newTemplateSettings.routing.rules = []; - this.templateSettings.routing.rules.forEach(routingRule => { - if (routingRule.hasOwnProperty('protocol')){ - if (routingRule.protocol[0] === "bittorrent" && routingRule.outboundTag == "blocked"){ - return; - } - } - newTemplateSettings.routing.rules.push(routingRule); - }); + this.templateSettings = newTemplateSettings; + } + }, + routingStrategy: { + get: function () { + if (!this.templateSettings || !this.templateSettings.routing || !this.templateSettings.routing.domainStrategy) return "AsIs"; + return this.templateSettings.routing.domainStrategy; + }, + set: function (newValue) { + newTemplateSettings = this.templateSettings; + newTemplateSettings.routing.domainStrategy = newValue; + this.templateSettings = newTemplateSettings; + } + }, + blockedIPs: { + get: function() { + return this.templateRuleGetter({outboundTag: "blocked", property: "ip"}); + }, + set: function(newValue) { + this.templateRuleSetter({outboundTag: "blocked", property: "ip", data: newValue}); + } + }, + blockedDomains: { + get: function() { + return this.templateRuleGetter({outboundTag: "blocked", property: "domain"}); + }, + set: function(newValue) { + this.templateRuleSetter({outboundTag: "blocked", property: "domain", data: newValue}); + } + }, + blockedProtocols: { + get: function() { + return this.templateRuleGetter({outboundTag: "blocked", property: "protocol"}); + }, + set: function(newValue) { + this.templateRuleSetter({outboundTag: "blocked", property: "protocol", data: newValue}); + } + }, + directIPs: { + get: function() { + return this.templateRuleGetter({outboundTag: "direct", property: "ip"}); + }, + set: function(newValue) { + this.templateRuleSetter({outboundTag: "direct", property: "ip", data: newValue}); + this.syncRulesWithOutbound("direct",this.directSettings); + } + }, + directDomains: { + get: function() { + return this.templateRuleGetter({outboundTag: "direct", property: "domain"}); + }, + set: function(newValue) { + this.templateRuleSetter({outboundTag: "direct", property: "domain", data: newValue}); + this.syncRulesWithOutbound("direct",this.directSettings); + } + }, + manualBlockedIPs: { + get: function() { return JSON.stringify(this.blockedIPs, null, 2); }, + set: debounce(function(value) { this.blockedIPs = JSON.parse(value); } , 1000) + }, + manualBlockedDomains: { + get: function() { return JSON.stringify(this.blockedDomains, null, 2); }, + set: debounce(function(value) { this.blockedDomains = JSON.parse(value); } , 1000) + }, + manualDirectIPs: { + get: function() { return JSON.stringify(this.directIPs, null, 2); }, + set: debounce(function(value) { this.directIPs = JSON.parse(value); } , 1000) + }, + manualDirectDomains: { + get: function() { return JSON.stringify(this.directDomains, null, 2); }, + set: debounce(function(value) { this.directDomains = JSON.parse(value); } , 1000) + }, + torrentSettings: { + get: function () { + return areAllItemsExist(this.settingsData.protocols.bittorrent,this.blockedProtocols); + }, + set: function (newValue) { + if (newValue) { + this.blockedProtocols = [...this.blockedProtocols, ...this.settingsData.protocols.bittorrent]; + } else { + this.blockedProtocols = this.blockedProtocols.filter(data => !this.settingsData.protocols.bittorrent.includes(data)); } - this.templateSettings = newTemplateSettings }, }, privateIpSettings: { get: function () { - localIpFilter = false - if(this.templateSettings != null){ - this.templateSettings.routing.rules.forEach(routingRule => { - if(routingRule.hasOwnProperty("ip")){ - if (routingRule.ip[0] === "geoip:private" && routingRule.outboundTag == "blocked"){ - localIpFilter = true - } - } - }); - } - return localIpFilter + return areAllItemsExist(this.settingsData.ips.local,this.blockedIPs); }, set: function (newValue) { - newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig); - if (newValue){ - newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"ip\": [\"geoip:private\"],\"type\": \"field\"}")) + if (newValue) { + this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.local]; + } else { + this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.local.includes(data)); } - else { - newTemplateSettings.routing.rules = []; - this.templateSettings.routing.rules.forEach(routingRule => { - if (routingRule.hasOwnProperty('ip')){ - if (routingRule.ip[0] === "geoip:private" && routingRule.outboundTag == "blocked"){ - return; - } - } - newTemplateSettings.routing.rules.push(routingRule); - }); - } - this.templateSettings = newTemplateSettings }, }, - } - }); - + AdsSettings: { + get: function () { + return areAllItemsExist(this.settingsData.domains.ads,this.blockedDomains); + }, + set: function (newValue) { + if (newValue) { + this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ads]; + } else { + this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ads.includes(data)); + } + }, + }, + familyProtectSettings: { + get: function () { + if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false; + return areAllItemsExist(this.templateSettings.dns.servers,this.settingsData.familyProtectDNS.servers); + }, + set: function (newValue) { + newTemplateSettings = this.templateSettings; + if (newValue) { + newTemplateSettings.dns = this.settingsData.familyProtectDNS; + } else { + delete newTemplateSettings.dns; + } + this.templateSettings = newTemplateSettings; + }, + }, + GoogleIPv4Settings: { + get: function () { + return areAllItemsExist(this.settingsData.domains.google, this.templateRuleGetter({outboundTag: "IPv4", property: "domain"})); + }, + set: function (newValue) { + oldData = this.templateRuleGetter({outboundTag: "IPv4", property: "domain"}); + if (newValue) { + oldData = [...oldData, ...this.settingsData.domains.google]; + } else { + oldData = oldData.filter(data => !this.settingsData.domains.google.includes(data)) + } + this.templateRuleSetter({ + outboundTag: "IPv4", + property: "domain", + data: oldData + }); + this.syncRulesWithOutbound("IPv4", this.ipv4Settings); + }, + }, + NetflixIPv4Settings: { + get: function () { + return areAllItemsExist(this.settingsData.domains.netflix, this.templateRuleGetter({outboundTag: "IPv4", property: "domain"})); + }, + set: function (newValue) { + oldData = this.templateRuleGetter({outboundTag: "IPv4", property: "domain"}); + if (newValue) { + oldData = [...oldData, ...this.settingsData.domains.netflix]; + } else { + oldData = oldData.filter(data => !this.settingsData.domains.netflix.includes(data)) + } + this.templateRuleSetter({ + outboundTag: "IPv4", + property: "domain", + data: oldData + }); + this.syncRulesWithOutbound("IPv4", this.ipv4Settings); + }, + }, + IRIpSettings: { + get: function () { + return areAllItemsExist(this.settingsData.ips.ir,this.blockedIPs); + }, + set: function (newValue) { + if (newValue) { + this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ir]; + } else { + this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ir.includes(data)); + } + } + }, + IRDomainSettings: { + get: function () { + return areAllItemsExist(this.settingsData.domains.ir,this.blockedDomains); + }, + set: function (newValue) { + if (newValue) { + this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ir]; + } else { + this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ir.includes(data)); + } + } + }, + ChinaIpSettings: { + get: function () { + return areAllItemsExist(this.settingsData.ips.cn,this.blockedIPs); + }, + set: function (newValue) { + if (newValue) { + this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.cn]; + } else { + this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.cn.includes(data)); + } + } + }, + ChinaDomainSettings: { + get: function () { + return areAllItemsExist(this.settingsData.domains.cn,this.blockedDomains); + }, + set: function (newValue) { + if (newValue) { + this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.cn]; + } else { + this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.cn.includes(data)); + } + } + }, + RussiaIpSettings: { + get: function () { + return areAllItemsExist(this.settingsData.ips.ru,this.blockedIPs); + }, + set: function (newValue) { + if (newValue) { + this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ru]; + } else { + this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ru.includes(data)); + } + } + }, + RussiaDomainSettings: { + get: function () { + return areAllItemsExist(this.settingsData.domains.ru,this.blockedDomains); + }, + set: function (newValue) { + if (newValue) { + this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ru]; + } else { + this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ru.includes(data)); + } + } + }, + IRIpDirectSettings: { + get: function () { + return areAllItemsExist(this.settingsData.ips.ir,this.directIPs); + }, + set: function (newValue) { + if (newValue) { + this.directIPs = [...this.directIPs, ...this.settingsData.ips.ir]; + } else { + this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ir.includes(data)); + } + } + }, + IRDomainDirectSettings: { + get: function () { + return areAllItemsExist(this.settingsData.domains.ir,this.directDomains); + }, + set: function (newValue) { + if (newValue) { + this.directDomains = [...this.directDomains, ...this.settingsData.domains.ir]; + } else { + this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ir.includes(data)); + } + } + }, + ChinaIpDirectSettings: { + get: function () { + return areAllItemsExist(this.settingsData.ips.cn,this.directIPs); + }, + set: function (newValue) { + if (newValue) { + this.directIPs = [...this.directIPs, ...this.settingsData.ips.cn]; + } else { + this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.cn.includes(data)); + } + } + }, + ChinaDomainDirectSettings: { + get: function () { + return areAllItemsExist(this.settingsData.domains.cn,this.directDomains); + }, + set: function (newValue) { + if (newValue) { + this.directDomains = [...this.directDomains, ...this.settingsData.domains.cn]; + } else { + this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.cn.includes(data)); + } + } + }, + RussiaIpDirectSettings: { + get: function () { + return areAllItemsExist(this.settingsData.ips.ru,this.directIPs); + }, + set: function (newValue) { + if (newValue) { + this.directIPs = [...this.directIPs, ...this.settingsData.ips.ru]; + } else { + this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ru.includes(data)); + } + } + }, + RussiaDomainDirectSettings: { + get: function () { + return areAllItemsExist(this.settingsData.domains.ru,this.directDomains); + }, + set: function (newValue) { + if (newValue) { + this.directDomains = [...this.directDomains, ...this.settingsData.domains.ru]; + } else { + this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ru.includes(data)); + } + } + }, + }, + }); \ No newline at end of file diff --git a/web/service/config.json b/web/service/config.json index 70f49136..a986ea94 100644 --- a/web/service/config.json +++ b/web/service/config.json @@ -2,7 +2,6 @@ "log": { "loglevel": "warning" }, - "api": { "services": [ "HandlerService", @@ -46,6 +45,7 @@ } }, "routing": { + "domainStrategy": "IPIfNonMatch", "rules": [ { "inboundTag": [