DHR60 2025-09-09 20:25:29 +08:00 committed by GitHub
commit d940c6f5a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 420 additions and 0 deletions

View File

@ -114,6 +114,33 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Core &apos;{0}&apos; does not support network type &apos;{1}&apos;. 的本地化字符串。
/// </summary>
public static string CoreNotSupportNetwork {
get {
return ResourceManager.GetString("CoreNotSupportNetwork", resourceCulture);
}
}
/// <summary>
/// 查找类似 Core &apos;{0}&apos; does not support protocol &apos;{1}&apos;. 的本地化字符串。
/// </summary>
public static string CoreNotSupportProtocol {
get {
return ResourceManager.GetString("CoreNotSupportProtocol", resourceCulture);
}
}
/// <summary>
/// 查找类似 Core &apos;{0}&apos; does not support protocol &apos;{1}&apos; when using transport &apos;{2}&apos;. 的本地化字符串。
/// </summary>
public static string CoreNotSupportProtocolTransport {
get {
return ResourceManager.GetString("CoreNotSupportProtocolTransport", resourceCulture);
}
}
/// <summary>
/// 查找类似 Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. 的本地化字符串。
/// </summary>
@ -2040,6 +2067,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Proxy chained node alias &apos;{0}&apos; does not exist. 的本地化字符串。
/// </summary>
public static string ProxyChainedNodeTagNotExist {
get {
return ResourceManager.GetString("ProxyChainedNodeTagNotExist", resourceCulture);
}
}
/// <summary>
/// 查找类似 Proxy chained node remark &apos;{0}&apos; refers to an outbound that does not support config type &apos;{1}&apos;. 的本地化字符串。
/// </summary>
public static string ProxyChainedNodeTagNotSupportConfigType {
get {
return ResourceManager.GetString("ProxyChainedNodeTagNotSupportConfigType", resourceCulture);
}
}
/// <summary>
/// 查找类似 Global hotkey {0} registration failed, reason: {1} 的本地化字符串。
/// </summary>
@ -2103,6 +2148,24 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Routing rule references outbound remark &apos;{0}&apos;, but no outbound with this remark exists. 的本地化字符串。
/// </summary>
public static string RoutingRuleOutboundTagNotExist {
get {
return ResourceManager.GetString("RoutingRuleOutboundTagNotExist", resourceCulture);
}
}
/// <summary>
/// 查找类似 Routing rule outbound remark &apos;{0}&apos; refers to an outbound that does not support config type &apos;{1}&apos;. 的本地化字符串。
/// </summary>
public static string RoutingRuleOutboundTagNotSupportConfigType {
get {
return ResourceManager.GetString("RoutingRuleOutboundTagNotSupportConfigType", resourceCulture);
}
}
/// <summary>
/// 查找类似 Run as Admin 的本地化字符串。
/// </summary>

View File

@ -1515,4 +1515,25 @@
<data name="TbSelectProfile" xml:space="preserve">
<value>Select Profile</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>Routing rule references outbound remark '{0}', but no outbound with this remark exists.</value>
</data>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>Proxy chained node remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>Proxy chained node alias '{0}' does not exist.</value>
</data>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>Routing rule outbound remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
</root>

View File

@ -1515,4 +1515,25 @@
<data name="TbSelectProfile" xml:space="preserve">
<value>Select Profile</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>Routing rule references outbound remark '{0}', but no outbound with this remark exists.</value>
</data>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>Proxy chained node remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>Proxy chained node alias '{0}' does not exist.</value>
</data>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>Routing rule outbound remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
</root>

View File

@ -1515,4 +1515,25 @@
<data name="TbSelectProfile" xml:space="preserve">
<value>Select Profile</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>Routing rule references outbound remark '{0}', but no outbound with this remark exists.</value>
</data>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>Proxy chained node remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>Proxy chained node alias '{0}' does not exist.</value>
</data>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>Routing rule outbound remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
</root>

View File

@ -1515,4 +1515,25 @@
<data name="TbSelectProfile" xml:space="preserve">
<value>Select Profile</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>Routing rule references outbound remark '{0}', but no outbound with this remark exists.</value>
</data>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>Proxy chained node remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>Proxy chained node alias '{0}' does not exist.</value>
</data>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>Routing rule outbound remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
</root>

View File

@ -1512,4 +1512,25 @@
<data name="TbSelectProfile" xml:space="preserve">
<value>选择配置文件</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支持网络类型 '{1}'。</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>核心 '{0}' 在使用传输方式 '{2}' 时不支持协议 '{1}'。</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>核心 '{0}' 不支持协议 '{1}'。</value>
</data>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>路由规则引用了出站别名 '{0}',但不存在具有该别名的出站。</value>
</data>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>代理链节点别名 '{0}' 引用的出站不支持配置类型 '{1}'。</value>
</data>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>代理链节点别名 '{0}' 不存在。</value>
</data>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>路由规则出站别名 '{0}' 引用的出站不支持配置类型 '{1}'。</value>
</data>
</root>

View File

@ -1512,4 +1512,25 @@
<data name="TbSelectProfile" xml:space="preserve">
<value>Select Profile</value>
</data>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
<data name="CoreNotSupportProtocolTransport" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'.</value>
</data>
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>Routing rule references outbound remark '{0}', but no outbound with this remark exists.</value>
</data>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>Proxy chained node remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>Proxy chained node alias '{0}' does not exist.</value>
</data>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>Routing rule outbound remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
</root>

View File

@ -0,0 +1,211 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DynamicData;
using ServiceLib.Models;
namespace ServiceLib.Services;
/// <summary>
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
/// Return (ok, msg) for VMs to decide.
/// </summary>
public class ActionPrecheckService
{
private static readonly Lazy<ActionPrecheckService> _instance = new(() => new ActionPrecheckService(AppManager.Instance.Config));
public static ActionPrecheckService Instance => _instance.Value;
private readonly Config _config;
public ActionPrecheckService(Config config)
{
_config = config;
}
private static List<string> OneMsg(string msg) => new() { msg };
public async Task<(bool ok, List<string> msgs)> CheckBeforeSetActive(string? indexId)
{
if (indexId.IsNullOrEmpty())
{
return (false, OneMsg(ResUI.PleaseSelectServer));
}
var item = await AppManager.Instance.GetProfileItem(indexId);
if (item is null)
{
return (false, OneMsg(ResUI.PleaseSelectServer));
}
return await CheckBeforeGenerateConfig(item);
}
public async Task<(bool ok, List<string> msgs)> CheckBeforeGenerateConfig(ProfileItem? item)
{
if (item is null)
{
return (false, OneMsg(ResUI.PleaseSelectServer));
}
var msgs = new List<string>();
var currentNodeMsgs = ValidateCurrentNodeAndCoreSupport(item).ToList();
if (currentNodeMsgs.Count > 0)
{
msgs.AddRange(currentNodeMsgs);
return (false, msgs);
}
msgs.AddRange(await ValidateRelatedNodesExistAndValid(item));
return (true, msgs);
}
private IEnumerable<string> ValidateCurrentNodeAndCoreSupport(ProfileItem item)
{
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
return ValidateNodeAndCoreSupport(item, coreType);
}
/// <summary>
/// <summary>
/// Validate whether the node and chosen core combination is supported. Returns a collection of messages to show the user.
/// An empty collection means there are no blocking errors.
/// </summary>
///
private IEnumerable<string> ValidateNodeAndCoreSupport(ProfileItem item, ECoreType? coreType = null)
{
// sing-box does not support xhttp / kcp
// sing-box does not support transports like ws/http/httpupgrade/etc. when the node is not vmess/trojan/vless
coreType ??= AppManager.Instance.GetCoreType(item, item.ConfigType);
var net = item.GetNetwork() ?? item.Network;
if (coreType == ECoreType.sing_box)
{
if (net is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
yield return string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net);
yield break;
}
if (item.ConfigType is not (EConfigType.VMess or EConfigType.VLESS or EConfigType.Trojan))
{
if (net is nameof(ETransport.ws) or nameof(ETransport.http) or nameof(ETransport.h2) or nameof(ETransport.quic) or nameof(ETransport.httpupgrade))
{
yield return string.Format(ResUI.CoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), item.ConfigType.ToString(), net);
yield break;
}
}
}
else if (coreType is ECoreType.Xray)
{
// Xray core does not support these protocols
if (item.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)
{
yield return string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString());
yield break;
}
}
yield break; // explicit for clarity; no blocking errors
}
/// <summary>
/// Validate that nodes related to the current node (chained/routing) exist and are valid.
/// </summary>
private async Task<IEnumerable<string>> ValidateRelatedNodesExistAndValid(ProfileItem? item)
{
var msgs = new List<string>();
msgs.AddRange(await ValidateProxyChainedNodeExistAndValid(item));
msgs.AddRange(await ValidateRoutingNodeExistAndValid(item));
return msgs;
}
private async Task<IEnumerable<string>> ValidateProxyChainedNodeExistAndValid(ProfileItem? item)
{
var msgs = new List<string>();
if (item is null)
{
return msgs;
}
// prev node and next node
var subItem = await AppManager.Instance.GetSubItem(item.Subid);
if (subItem is null)
{
return msgs;
}
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
CollectProxyChainedNodeValidation(prevNode, subItem.PrevProfile, coreType, msgs);
CollectProxyChainedNodeValidation(nextNode, subItem.NextProfile, coreType, msgs);
return msgs;
}
private void CollectProxyChainedNodeValidation(ProfileItem? node, string tag, ECoreType coreType, List<string> msgs)
{
if (node is not null)
{
msgs.AddRange(ValidateNodeAndCoreSupport(node, coreType));
if (node.ConfigType is EConfigType.Custom)
{
msgs.Add(string.Format(ResUI.ProxyChainedNodeTagNotSupportConfigType, node.Remarks, node.ConfigType.ToString()));
}
}
else if (tag.IsNotEmpty())
{
msgs.Add(string.Format(ResUI.ProxyChainedNodeTagNotExist, tag));
}
}
private async Task<IEnumerable<string>> ValidateRoutingNodeExistAndValid(ProfileItem? item)
{
var msgs = new List<string>();
if (item is null)
{
return msgs;
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing == null)
{
return msgs;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var ruleItem in rules ?? [])
{
if (!ruleItem.Enabled)
{
continue;
}
var outboundTag = ruleItem.OutboundTag;
if (outboundTag.IsNullOrEmpty() || Global.OutboundTags.Contains(outboundTag))
{
continue;
}
var tagItem = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
if (tagItem is null)
{
msgs.Add(string.Format(ResUI.RoutingRuleOutboundTagNotExist, outboundTag));
continue;
}
msgs.AddRange(ValidateNodeAndCoreSupport(tagItem, coreType));
if (tagItem.ConfigType is EConfigType.Custom)
{
msgs.Add(string.Format(ResUI.RoutingRuleOutboundTagNotSupportConfigType, outboundTag, tagItem.ConfigType.ToString()));
}
}
return msgs;
}
}

View File

@ -594,6 +594,16 @@ public class ProfilesViewModel : MyReactiveObject
return;
}
var (ok, msgs) = await ActionPrecheckService.Instance.CheckBeforeSetActive(indexId);
if (msgs.Count > 0)
{
NoticeManager.Instance.Enqueue(msgs.First());
}
if (!ok)
{
return;
}
if (await ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0)
{
await RefreshServers();
@ -757,6 +767,16 @@ public class ProfilesViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
return;
}
var (ok, msgs) = await ActionPrecheckService.Instance.CheckBeforeGenerateConfig(item);
if (msgs.Count > 0)
{
NoticeManager.Instance.Enqueue(msgs.First());
}
if (!ok)
{
return;
}
if (blClipboard)
{
var result = await CoreConfigHandler.GenerateClientConfig(item, null);