Merge branch 'master' into master

This commit is contained in:
freekof 2026-03-03 10:30:12 +08:00 committed by GitHub
commit fa9709da07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 617 additions and 648 deletions

View file

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.19.0</Version> <Version>7.19.1</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -22,7 +22,7 @@
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.3" /> <PackageVersion Include="Semi.Avalonia" Version="11.3.7.3" />
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" /> <PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" /> <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
<PackageVersion Include="NLog" Version="6.1.0" /> <PackageVersion Include="NLog" Version="6.1.1" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" /> <PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" /> <PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" /> <PackageVersion Include="WebDav.Client" Version="2.9.0" />

View file

@ -497,6 +497,13 @@ public class Utils
return false; return false;
} }
var ext = Path.GetExtension(domain);
if (ext.IsNotEmpty()
&& ext[1..].ToLowerInvariant() is "json" or "txt" or "xml" or "cfg" or "ini" or "log" or "yaml" or "yml" or "toml")
{
return false;
}
return Uri.CheckHostName(domain) == UriHostNameType.Dns; return Uri.CheckHostName(domain) == UriHostNameType.Dns;
} }

View file

@ -49,6 +49,7 @@ public class Global
public const string DirectTag = "direct"; public const string DirectTag = "direct";
public const string BlockTag = "block"; public const string BlockTag = "block";
public const string DnsTag = "dns-module"; public const string DnsTag = "dns-module";
public const string DirectDnsTag = "direct-dns";
public const string BalancerTagSuffix = "-round"; public const string BalancerTagSuffix = "-round";
public const string StreamSecurity = "tls"; public const string StreamSecurity = "tls";
public const string StreamSecurityReality = "reality"; public const string StreamSecurityReality = "reality";
@ -90,6 +91,22 @@ public class Global
public const int Hysteria2DefaultHopInt = 10; public const int Hysteria2DefaultHopInt = 10;
public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset";
public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$";
public static readonly List<string> PolicyGroupDefaultFilterList =
[
// All nodes (exclude traffic/expiry info)
PolicyGroupDefaultAllFilter,
// Low multiplier nodes, e.g. ×0.1, 0.5x, 0.1倍
@"^.*(?:[×xX✕*]\s*0\.[0-9]+|0\.[0-9]+\s*[×xX✕*倍]).*$",
// Dedicated line nodes, e.g. IPLC, IEPL
$@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:专线|IPLC|IEPL|中转).*$",
// Japan nodes
$@"^(?!.*(?:{PolicyGroupExcludeKeywords})).*(?:日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan).*$",
];
public static readonly List<string> IEProxyProtocols = public static readonly List<string> IEProxyProtocols =
[ [
"{ip}:{http_port}", "{ip}:{http_port}",

View file

@ -5,6 +5,39 @@ public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeVali
public bool Success => ValidatorResult.Success; public bool Success => ValidatorResult.Success;
} }
/// <summary>
/// Holds the results of a full context build, including the main context and an optional
/// pre-socks context (e.g. for TUN protection or pre-socks chaining).
/// </summary>
public record CoreConfigContextBuilderAllResult(
CoreConfigContextBuilderResult MainResult,
CoreConfigContextBuilderResult? PreSocksResult)
{
/// <summary>True only when both the main result and (if present) the pre-socks result succeeded.</summary>
public bool Success => MainResult.Success && (PreSocksResult?.Success ?? true);
/// <summary>
/// Merges all errors and warnings from the main result and the optional pre-socks result
/// into a single <see cref="NodeValidatorResult"/> for unified notification.
/// </summary>
public NodeValidatorResult CombinedValidatorResult => new(
[.. MainResult.ValidatorResult.Errors, .. PreSocksResult?.ValidatorResult.Errors ?? []],
[.. MainResult.ValidatorResult.Warnings, .. PreSocksResult?.ValidatorResult.Warnings ?? []]);
/// <summary>
/// The main context with TunProtectSsPort/ProxyRelaySsPort and ProtectDomainList merged in
/// from the pre-socks result (if any). Pass this to the core runner.
/// </summary>
public CoreConfigContext ResolvedMainContext => PreSocksResult is not null
? MainResult.Context with
{
TunProtectSsPort = PreSocksResult.Context.TunProtectSsPort,
ProxyRelaySsPort = PreSocksResult.Context.ProxyRelaySsPort,
ProtectDomainList = [.. MainResult.Context.ProtectDomainList ?? [], .. PreSocksResult.Context.ProtectDomainList ?? []],
}
: MainResult.Context;
}
public class CoreConfigContextBuilder public class CoreConfigContextBuilder
{ {
/// <summary> /// <summary>
@ -75,6 +108,79 @@ public class CoreConfigContextBuilder
return new CoreConfigContextBuilderResult(context, validatorResult); return new CoreConfigContextBuilderResult(context, validatorResult);
} }
/// <summary>
/// Builds the main <see cref="CoreConfigContext"/> for <paramref name="node"/> and, when
/// the main build succeeds, also builds the optional pre-socks context required for TUN
/// protection or pre-socks proxy chaining.
/// </summary>
public static async Task<CoreConfigContextBuilderAllResult> BuildAll(Config config, ProfileItem node)
{
var mainResult = await Build(config, node);
if (!mainResult.Success)
{
return new CoreConfigContextBuilderAllResult(mainResult, null);
}
var preResult = await BuildPreSocksIfNeeded(mainResult.Context);
return new CoreConfigContextBuilderAllResult(mainResult, preResult);
}
/// <summary>
/// Determines whether a pre-socks context is required for <paramref name="nodeContext"/>
/// and, if so, builds and returns it. Returns <c>null</c> when no pre-socks core is needed.
/// </summary>
private static async Task<CoreConfigContextBuilderResult?> BuildPreSocksIfNeeded(CoreConfigContext nodeContext)
{
var config = nodeContext.AppConfig;
var node = nodeContext.Node;
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var preSocksItem = ConfigHandler.GetPreSocksItem(config, node, coreType);
if (preSocksItem != null)
{
var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
return preSocksResult with
{
Context = preSocksResult.Context with
{
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
}
};
}
if (!nodeContext.IsTunEnabled
|| coreType != ECoreType.Xray
|| node.ConfigType == EConfigType.Custom)
{
return null;
}
var tunProtectSsPort = Utils.GetFreePort();
var proxyRelaySsPort = Utils.GetFreePort();
var preItem = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.Shadowsocks,
Address = Global.Loopback,
Port = proxyRelaySsPort,
Password = Global.None,
};
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
{
SsMethod = Global.None,
});
var preResult2 = await Build(nodeContext.AppConfig, preItem);
return preResult2 with
{
Context = preResult2.Context with
{
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preResult2.Context.ProtectDomainList ?? []],
TunProtectSsPort = tunProtectSsPort,
ProxyRelaySsPort = proxyRelaySsPort,
}
};
}
/// <summary> /// <summary>
/// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain. /// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain.
/// Returns the effective (possibly replaced) node and the validation result. /// Returns the effective (possibly replaced) node and the validation result.

View file

@ -231,6 +231,7 @@ public static class ConfigHandler
item.Address = profileItem.Address; item.Address = profileItem.Address;
item.Port = profileItem.Port; item.Port = profileItem.Port;
item.Username = profileItem.Username;
item.Password = profileItem.Password; item.Password = profileItem.Password;
item.Network = profileItem.Network; item.Network = profileItem.Network;
@ -1165,46 +1166,28 @@ public static class ConfigHandler
/// <summary> /// <summary>
/// Create a group server that combines multiple servers for load balancing /// Create a group server that combines multiple servers for load balancing
/// Generates a configuration file that references multiple servers /// Generates a PolicyGroup profile with references to the sub-items
/// </summary> /// </summary>
/// <param name="config">Current configuration</param> /// <param name="config">Current configuration</param>
/// <param name="selecteds">Selected servers to combine</param> /// <param name="subItem">Sub-item for grouping</param>
/// <param name="coreType">Core type to use (Xray or sing_box)</param>
/// <param name="multipleLoad">Load balancing algorithm</param>
/// <returns>Result object with success state and data</returns> /// <returns>Result object with success state and data</returns>
public static async Task<RetResult> AddGroupServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad, string? subId) public static async Task<RetResult> AddGroupAllServer(Config config, SubItem? subItem)
{ {
var result = new RetResult(); var result = new RetResult();
var indexId = Utils.GetGuid(false); var subId = subItem?.Id;
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList()); if (subId.IsNullOrEmpty())
{
result.Success = false;
return result;
}
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId))?.Remarks} "; var indexId = Utils.GetGuid(false);
if (coreType == ECoreType.Xray) var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup}";
{
remark += multipleLoad switch
{
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerXrayFallback,
EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom,
EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad,
_ => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
};
}
else if (coreType == ECoreType.sing_box)
{
remark += multipleLoad switch
{
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback,
_ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
};
}
var profile = new ProfileItem var profile = new ProfileItem
{ {
IndexId = indexId, IndexId = indexId,
CoreType = coreType, CoreType = ECoreType.Xray,
ConfigType = EConfigType.PolicyGroup, ConfigType = EConfigType.PolicyGroup,
Remarks = remark, Remarks = remark,
IsSub = false IsSub = false
@ -1215,8 +1198,10 @@ public static class ConfigHandler
} }
var extraItem = new ProtocolExtraItem var extraItem = new ProtocolExtraItem
{ {
ChildItems = childProfileIndexId, MultipleLoad = EMultipleLoad.LeastPing,
MultipleLoad = multipleLoad, GroupType = profile.ConfigType.ToString(),
SubChildItems = subId,
Filter = Global.PolicyGroupDefaultAllFilter,
}; };
profile.SetProtocolExtra(extraItem); profile.SetProtocolExtra(extraItem);
var ret = await AddServerCommon(config, profile, true); var ret = await AddServerCommon(config, profile, true);
@ -1225,6 +1210,92 @@ public static class ConfigHandler
return result; return result;
} }
private static string CombineWithDefaultAllFilter(string regionPattern)
{
return $"^(?!.*(?:{Global.PolicyGroupExcludeKeywords})).*(?:{regionPattern}).*$";
}
private static readonly Dictionary<string, string> PolicyGroupRegionFilters = new()
{
{ "JP", "日本|\\b[Jj][Pp]\\b|🇯🇵|[Jj]apan" },
{ "US", "美国|\\b[Uu][Ss]\\b|🇺🇸|[Uu]nited [Ss]tates|\\b[Uu][Ss][Aa]\\b" },
{ "HK", "香港|\\b[Hh][Kk]\\b|🇭🇰|[Hh]ong ?[Kk]ong" },
{ "TW", "台湾|台灣|\\b[Tt][Ww]\\b|🇹🇼|[Tt]aiwan" },
{ "KR", "韩国|\\b[Kk][Rr]\\b|🇰🇷|[Kk]orea" },
{ "SG", "新加坡|\\b[Ss][Gg]\\b|🇸🇬|[Ss]ingapore" },
{ "DE", "德国|\\b[Dd][Ee]\\b|🇩🇪|[Gg]ermany" },
{ "FR", "法国|\\b[Ff][Rr]\\b|🇫🇷|[Ff]rance" },
{ "GB", "英国|\\b[Gg][Bb]\\b|🇬🇧|[Uu]nited [Kk]ingdom|[Bb]ritain" },
{ "CA", "加拿大|🇨🇦|[Cc]anada" },
{ "AU", "澳大利亚|\\b[Aa][Uu]\\b|🇦🇺|[Aa]ustralia" },
{ "RU", "俄罗斯|\\b[Rr][Uu]\\b|🇷🇺|[Rr]ussia" },
{ "BR", "巴西|\\b[Bb][Rr]\\b|🇧🇷|[Bb]razil" },
{ "IN", "印度|🇮🇳|[Ii]ndia" },
{ "VN", "越南|\\b[Vv][Nn]\\b|🇻🇳|[Vv]ietnam" },
{ "ID", "印度尼西亚|\\b[Ii][Dd]\\b|🇮🇩|[Ii]ndonesia" },
{ "MX", "墨西哥|\\b[Mm][Xx]\\b|🇲🇽|[Mm]exico" }
};
public static async Task<RetResult> AddGroupRegionServer(Config config, SubItem? subItem)
{
var result = new RetResult();
var subId = subItem?.Id;
if (subId.IsNullOrEmpty())
{
result.Success = false;
return result;
}
var childProfiles = await AppManager.Instance.ProfileItems(subId);
List<string> indexIdList = [];
foreach (var regionFilter in PolicyGroupRegionFilters)
{
var indexId = Utils.GetGuid(false);
var remark = $"{subItem.Remarks} - {ResUI.TbConfigTypePolicyGroup} - {regionFilter.Key}";
var profile = new ProfileItem
{
IndexId = indexId,
CoreType = ECoreType.Xray,
ConfigType = EConfigType.PolicyGroup,
Remarks = remark,
IsSub = false
};
if (!subId.IsNullOrEmpty())
{
profile.Subid = subId;
}
var extraItem = new ProtocolExtraItem
{
MultipleLoad = EMultipleLoad.LeastPing,
GroupType = profile.ConfigType.ToString(),
SubChildItems = subId,
Filter = CombineWithDefaultAllFilter(regionFilter.Value),
};
profile.SetProtocolExtra(extraItem);
var matchedChildProfiles = childProfiles?.Where(p =>
p != null &&
p.IsValid() &&
!p.ConfigType.IsComplexType() &&
(extraItem.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extraItem.Filter))
)
.ToList() ?? [];
if (matchedChildProfiles.Count == 0)
{
continue;
}
var ret = await AddServerCommon(config, profile, true);
if (ret == 0)
{
indexIdList.Add(indexId);
}
}
result.Success = indexIdList.Count > 0;
result.Data = indexIdList;
return result;
}
/// <summary> /// <summary>
/// Get a SOCKS server profile for pre-SOCKS functionality /// Get a SOCKS server profile for pre-SOCKS functionality
/// Used when TUN mode is enabled or when a custom config has a pre-SOCKS port /// Used when TUN mode is enabled or when a custom config has a pre-SOCKS port
@ -1251,47 +1322,6 @@ public static class ConfigHandler
return itemSocks; return itemSocks;
} }
public static CoreConfigContext? GetPreSocksCoreConfigContext(CoreConfigContext nodeContext)
{
var config = nodeContext.AppConfig;
var node = nodeContext.Node;
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var preSocksItem = GetPreSocksItem(config, node, coreType);
if (preSocksItem != null)
{
return nodeContext with { Node = preSocksItem, };
}
if ((!nodeContext.IsTunEnabled)
|| coreType != ECoreType.Xray
|| node.ConfigType == EConfigType.Custom)
{
return null;
}
var tunProtectSsPort = Utils.GetFreePort();
var proxyRelaySsPort = Utils.GetFreePort();
var preItem = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.Shadowsocks,
Address = Global.Loopback,
Port = proxyRelaySsPort,
Password = Global.None,
};
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
{
SsMethod = Global.None,
});
var preContext = nodeContext with
{
Node = preItem,
TunProtectSsPort = tunProtectSsPort,
ProxyRelaySsPort = proxyRelaySsPort,
};
return preContext;
}
/// <summary> /// <summary>
/// Remove servers with invalid test results (timeout) /// Remove servers with invalid test results (timeout)
/// Useful for cleaning up subscription lists /// Useful for cleaning up subscription lists

View file

@ -99,11 +99,9 @@ public static class CoreConfigHandler
}; };
var builderResult = await CoreConfigContextBuilder.Build(config, dummyNode); var builderResult = await CoreConfigContextBuilder.Build(config, dummyNode);
var context = builderResult.Context; var context = builderResult.Context;
var ids = selecteds.Where(serverTestItem => !serverTestItem.IndexId.IsNullOrEmpty()) foreach (var testItem in selecteds)
.Select(serverTestItem => serverTestItem.IndexId);
var nodes = await AppManager.Instance.GetProfileItemsByIndexIds(ids);
foreach (var node in nodes)
{ {
var node = testItem.Profile;
var (actNode, _) = await CoreConfigContextBuilder.ResolveNodeAsync(context, node, true); var (actNode, _) = await CoreConfigContextBuilder.ResolveNodeAsync(context, node, true);
if (node.IndexId == actNode.IndexId) if (node.IndexId == actNode.IndexId)
{ {

View file

@ -242,6 +242,30 @@ public sealed class AppManager
.ToListAsync(); .ToListAsync();
} }
public async Task<Dictionary<string, ProfileItem>> GetProfileItemsByIndexIdsAsMap(IEnumerable<string> indexIds)
{
var items = await GetProfileItemsByIndexIds(indexIds);
return items.ToDictionary(it => it.IndexId);
}
public async Task<List<ProfileItem>> GetProfileItemsOrderedByIndexIds(IEnumerable<string> indexIds)
{
var idList = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList();
if (idList.Count == 0)
{
return [];
}
var items = await SQLiteHelper.Instance.TableAsync<ProfileItem>()
.Where(it => idList.Contains(it.IndexId))
.ToListAsync();
var itemMap = items.ToDictionary(it => it.IndexId);
return idList.Select(id => itemMap.GetValueOrDefault(id))
.Where(item => item != null)
.ToList();
}
public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks) public async Task<ProfileItem?> GetProfileItemViaRemarks(string? remarks)
{ {
if (remarks.IsNullOrEmpty()) if (remarks.IsNullOrEmpty())

View file

@ -57,27 +57,19 @@ public class CoreManager
} }
} }
public async Task LoadCore(CoreConfigContext? context) /// <param name="mainContext">Resolved main context (with pre-socks ports already merged if applicable).</param>
/// <param name="preContext">Optional pre-socks context passed to <see cref="CoreStartPreService"/>.</param>
public async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{ {
if (context == null) if (mainContext == null)
{ {
await UpdateFunc(false, ResUI.CheckServerSettings); await UpdateFunc(false, ResUI.CheckServerSettings);
return; return;
} }
var contextMod = context; var node = mainContext.Node;
var node = contextMod.Node;
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
var preContext = ConfigHandler.GetPreSocksCoreConfigContext(contextMod); var result = await CoreConfigHandler.GenerateClientConfig(mainContext, fileName);
if (preContext is not null)
{
contextMod = contextMod with
{
TunProtectSsPort = preContext.TunProtectSsPort,
ProxyRelaySsPort = preContext.ProxyRelaySsPort,
};
}
var result = await CoreConfigHandler.GenerateClientConfig(contextMod, fileName);
if (result.Success != true) if (result.Success != true)
{ {
await UpdateFunc(true, result.Msg); await UpdateFunc(true, result.Msg);
@ -96,7 +88,7 @@ public class CoreManager
await WindowsUtils.RemoveTunDevice(); await WindowsUtils.RemoveTunDevice();
} }
await CoreStart(contextMod); await CoreStart(mainContext);
await CoreStartPreService(preContext); await CoreStartPreService(preContext);
if (_processService != null) if (_processService != null)
{ {
@ -106,7 +98,7 @@ public class CoreManager
public async Task<ProcessService?> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds) public async Task<ProcessService?> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
{ {
var coreType = selecteds.Any(t => Global.SingboxOnlyConfigType.Contains(t.ConfigType)) ? ECoreType.sing_box : ECoreType.Xray; var coreType = selecteds.FirstOrDefault()?.CoreType == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray;
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
var configPath = Utils.GetBinConfigPath(fileName); var configPath = Utils.GetBinConfigPath(fileName);
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType); var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, configPath, selecteds, coreType);

View file

@ -52,10 +52,10 @@ public class GroupProfileManager
return false; return false;
} }
foreach (var child in childIds) var childItems = await AppManager.Instance.GetProfileItemsByIndexIds(childIds);
foreach (var childItem in childItems)
{ {
var childItem = await AppManager.Instance.GetProfileItem(child); if (await HasCycle(childItem.IndexId, childItem?.GetProtocolExtra(), visited, stack))
if (await HasCycle(child, childItem?.GetProtocolExtra(), visited, stack))
{ {
return true; return true;
} }
@ -103,26 +103,7 @@ public class GroupProfileManager
return []; return [];
} }
var childProfiles = await AppManager.Instance.GetProfileItemsByIndexIds(childProfileIds); var ordered = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childProfileIds);
if (childProfiles == null || childProfiles.Count == 0)
{
return [];
}
var profileMap = childProfiles
.Where(p => p != null && !p.IndexId.IsNullOrEmpty())
.GroupBy(p => p!.IndexId!)
.ToDictionary(g => g.Key, g => g.First());
var ordered = new List<ProfileItem>(childProfileIds.Count);
foreach (var id in childProfileIds)
{
if (id != null && profileMap.TryGetValue(id, out var item) && item != null)
{
ordered.Add(item);
}
}
return ordered; return ordered;
} }

View file

@ -38,4 +38,25 @@ public class NoticeManager
Enqueue(msg); Enqueue(msg);
SendMessage(msg); SendMessage(msg);
} }
/// <summary>
/// Sends each error and warning in <paramref name="validatorResult"/> to the message panel
/// and enqueues a summary snack notification (capped at 10 messages).
/// Returns <c>true</c> when there were any messages so the caller can decide on early-return
/// based on <see cref="NodeValidatorResult.Success"/>.
/// </summary>
public bool NotifyValidatorResult(NodeValidatorResult validatorResult)
{
var msgs = new List<string>([.. validatorResult.Errors, .. validatorResult.Warnings]);
if (msgs.Count == 0)
{
return false;
}
foreach (var msg in msgs)
{
SendMessage(msg);
}
Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
return true;
}
} }

View file

@ -10,4 +10,6 @@ public class ServerTestItem
public bool AllowTest { get; set; } public bool AllowTest { get; set; }
public bool NeedAutoFillRemarks { get; set; } public bool NeedAutoFillRemarks { get; set; }
public int QueueNum { get; set; } public int QueueNum { get; set; }
public required ProfileItem Profile { get; set; }
public ECoreType CoreType { get; set; }
} }

View file

@ -220,12 +220,6 @@ public class DnsServer4Ray
public List<string>? domains { get; set; } public List<string>? domains { get; set; }
public bool? skipFallback { get; set; } public bool? skipFallback { get; set; }
public List<string>? expectedIPs { get; set; } public List<string>? expectedIPs { get; set; }
public List<string>? unexpectedIPs { get; set; }
public string? clientIp { get; set; }
public string? queryStrategy { get; set; }
public int? timeoutMs { get; set; }
public bool? disableCache { get; set; }
public bool? finalQuery { get; set; }
public string? tag { get; set; } public string? tag { get; set; }
} }

View file

@ -834,6 +834,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 All configurations 的本地化字符串。
/// </summary>
public static string menuAllServers {
get {
return ResourceManager.GetString("menuAllServers", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Backup and Restore 的本地化字符串。 /// 查找类似 Backup and Restore 的本地化字符串。
/// </summary> /// </summary>
@ -1006,74 +1015,20 @@ namespace ServiceLib.Resx {
} }
/// <summary> /// <summary>
/// 查找类似 Generate Policy Group from Multiple Profiles 的本地化字符串。 /// 查找类似 Generate Policy Group 的本地化字符串。
/// </summary> /// </summary>
public static string menuGenGroupMultipleServer { public static string menuGenGroupServer {
get { get {
return ResourceManager.GetString("menuGenGroupMultipleServer", resourceCulture); return ResourceManager.GetString("menuGenGroupServer", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// 查找类似 Fallback by sing-box 的本地化字符串。 /// 查找类似 Group by Region 的本地化字符串。
/// </summary> /// </summary>
public static string menuGenGroupMultipleServerSingBoxFallback { public static string menuGenRegionGroup {
get { get {
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxFallback", resourceCulture); return ResourceManager.GetString("menuGenRegionGroup", resourceCulture);
}
}
/// <summary>
/// 查找类似 LeastPing by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxLeastPing {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fallback by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayFallback {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayFallback", resourceCulture);
}
}
/// <summary>
/// 查找类似 LeastLoad by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastLoad {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastLoad", resourceCulture);
}
}
/// <summary>
/// 查找类似 LeastPing by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayLeastPing {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastPing", resourceCulture);
}
}
/// <summary>
/// 查找类似 Random by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRandom {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRandom", resourceCulture);
}
}
/// <summary>
/// 查找类似 RoundRobin by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayRoundRobin {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRoundRobin", resourceCulture);
} }
} }

View file

@ -1371,24 +1371,6 @@
<data name="TbPorts7Tips" xml:space="preserve"> <data name="TbPorts7Tips" xml:space="preserve">
<value>مخفی و پورت می شود، با کاما (،) جدا می شود</value> <value>مخفی و پورت می شود، با کاما (،) جدا می شود</value>
</data> </data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>چند سرور تصادفی توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>چند سرور RoundRobin توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>چند سرور LeastPing توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>چند سرور LeastLoad توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>LeastPing چند سرور توسط sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>صادر کردن سرور</value> <value>صادر کردن سرور</value>
</data> </data>
@ -1533,12 +1515,6 @@
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>Fallback</value> <value>Fallback</value>
</data> </data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve"> <data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'</value> <value>Core '{0}' does not support network type '{1}'</value>
</data> </data>
@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve"> <data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value> <value>Subscription next proxy {0} not found. Skipping.</value>
</data> </data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
</root> </root>

View file

@ -1368,24 +1368,6 @@
<data name="TbPorts7Tips" xml:space="preserve"> <data name="TbPorts7Tips" xml:space="preserve">
<value>Écrase le port ; pour plusieurs groupes, séparer par virgules (,)</value> <value>Écrase le port ; pour plusieurs groupes, séparer par virgules (,)</value>
</data> </data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Générer un groupe de stratégie depuis plusieurs profils</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Xray aléatoire (multi-sélection)</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Xray équilibrage (tourniquet) multi-sélection</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Xray latence minimale (multi-sélection)</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Xray le plus stable (multi-sélection)</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>sing-box latence minimale (multi-sélection)</value>
</data>
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>Exporter</value> <value>Exporter</value>
</data> </data>
@ -1530,12 +1512,6 @@
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>Basculement (failover)</value> <value>Basculement (failover)</value>
</data> </data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>sing-box basculement (multi-sélection)</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Xray basculement (multi-sélection)</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve"> <data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Le cœur « {0} » ne prend pas en charge le type de réseau « {1} »</value> <value>Le cœur « {0} » ne prend pas en charge le type de réseau « {1} »</value>
</data> </data>
@ -1683,4 +1659,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve"> <data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value> <value>Subscription next proxy {0} not found. Skipping.</value>
</data> </data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
</root> </root>

View file

@ -1371,24 +1371,6 @@
<data name="TbPorts7Tips" xml:space="preserve"> <data name="TbPorts7Tips" xml:space="preserve">
<value>A portot lefedi, vesszővel (,) elválasztva</value> <value>A portot lefedi, vesszővel (,) elválasztva</value>
</data> </data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Több konfiguráció véletlenszerűen Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Több konfiguráció RoundRobin Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Több konfiguráció legkisebb pinggel Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Több konfiguráció legkisebb terheléssel Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Több konfiguráció legkisebb pinggel sing-box szerint</value>
</data>
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>Konfiguráció exportálása</value> <value>Konfiguráció exportálása</value>
</data> </data>
@ -1533,12 +1515,6 @@
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>Fallback</value> <value>Fallback</value>
</data> </data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve"> <data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'</value> <value>Core '{0}' does not support network type '{1}'</value>
</data> </data>
@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve"> <data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value> <value>Subscription next proxy {0} not found. Skipping.</value>
</data> </data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
</root> </root>

View file

@ -1371,24 +1371,6 @@
<data name="TbPorts7Tips" xml:space="preserve"> <data name="TbPorts7Tips" xml:space="preserve">
<value>Will cover the port, separate with commas (,)</value> <value>Will cover the port, separate with commas (,)</value>
</data> </data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Random by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>RoundRobin by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>LeastPing by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>LeastLoad by Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>LeastPing by sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>Export</value> <value>Export</value>
</data> </data>
@ -1533,12 +1515,6 @@
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>Fallback</value> <value>Fallback</value>
</data> </data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Fallback by Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve"> <data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'</value> <value>Core '{0}' does not support network type '{1}'</value>
</data> </data>
@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve"> <data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value> <value>Subscription next proxy {0} not found. Skipping.</value>
</data> </data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
</root> </root>

View file

@ -1371,24 +1371,6 @@
<data name="TbPorts7Tips" xml:space="preserve"> <data name="TbPorts7Tips" xml:space="preserve">
<value>Заменит указанный порт, перечисляйте через запятую (,)</value> <value>Заменит указанный порт, перечисляйте через запятую (,)</value>
</data> </data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>Случайный (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Круговой (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>Минимальное RTT (минимальное время туда-обратно) (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Минимальная нагрузка (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Минимальное RTT (минимальное время туда-обратно) (sing-box)</value>
</data>
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>Экспортировать конфигурацию</value> <value>Экспортировать конфигурацию</value>
</data> </data>
@ -1533,12 +1515,6 @@
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>Fallback</value> <value>Fallback</value>
</data> </data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve"> <data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'</value> <value>Core '{0}' does not support network type '{1}'</value>
</data> </data>
@ -1686,4 +1662,13 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve"> <data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value> <value>Subscription next proxy {0} not found. Skipping.</value>
</data> </data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
</root> </root>

View file

@ -1368,24 +1368,6 @@
<data name="TbPorts7Tips" xml:space="preserve"> <data name="TbPorts7Tips" xml:space="preserve">
<value>会覆盖端口,多组时用逗号 (,) 隔开</value> <value>会覆盖端口,多组时用逗号 (,) 隔开</value>
</data> </data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>多选生成策略组</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>多选随机 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多选负载均衡 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>多选最低延迟 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多选最稳定 Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多选最低延迟 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>导出</value> <value>导出</value>
</data> </data>
@ -1530,12 +1512,6 @@
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>故障转移</value> <value>故障转移</value>
</data> </data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>多选故障转移 sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>多选故障转移 Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve"> <data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支持网络类型 '{1}'</value> <value>核心 '{0}' 不支持网络类型 '{1}'</value>
</data> </data>
@ -1683,4 +1659,13 @@
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve"> <data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>订阅后置节点 {0} 未找到,已跳过。</value> <value>订阅后置节点 {0} 未找到,已跳过。</value>
</data> </data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>一键生成策略组</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>全部配置项</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>按地区分组</value>
</data>
</root> </root>

View file

@ -127,7 +127,7 @@
<value>設定格式不正確</value> <value>設定格式不正確</value>
</data> </data>
<data name="CustomServerTips" xml:space="preserve"> <data name="CustomServerTips" xml:space="preserve">
<value>注意,自訂設定完全依賴您自己的設定,不能使用所有設定功能。如需使用系統代理請手動修改偵聽埠。</value> <value>注意,自訂設定完全依賴您自行輸入的內容,部分功能可能無法使用。如需啟用系統代理,請手動調整監聽埠。</value>
</data> </data>
<data name="Downloading" xml:space="preserve"> <data name="Downloading" xml:space="preserve">
<value>下載開始...</value> <value>下載開始...</value>
@ -139,7 +139,7 @@
<value>生成預設設定檔失敗</value> <value>生成預設設定檔失敗</value>
</data> </data>
<data name="FailedGetDefaultConfiguration" xml:space="preserve"> <data name="FailedGetDefaultConfiguration" xml:space="preserve">
<value>取預設設定失敗</value> <value>取預設設定失敗</value>
</data> </data>
<data name="FailedImportedCustomServer" xml:space="preserve"> <data name="FailedImportedCustomServer" xml:space="preserve">
<value>匯入自訂設定失敗</value> <value>匯入自訂設定失敗</value>
@ -148,7 +148,7 @@
<value>讀取設定失敗</value> <value>讀取設定失敗</value>
</data> </data>
<data name="FillCorrectServerPort" xml:space="preserve"> <data name="FillCorrectServerPort" xml:space="preserve">
<value>請填寫正確格式的埠</value> <value>請填寫有效的埠號</value>
</data> </data>
<data name="FillLocalListeningPort" xml:space="preserve"> <data name="FillLocalListeningPort" xml:space="preserve">
<value>請填寫本機偵聽埠</value> <value>請填寫本機偵聽埠</value>
@ -247,7 +247,7 @@
<value>非 VMess 或 SS 協定</value> <value>非 VMess 或 SS 協定</value>
</data> </data>
<data name="NotFoundCore" xml:space="preserve"> <data name="NotFoundCore" xml:space="preserve">
<value>在資料夾 ({0}) 下未找到 Core 檔案 (檔案名: {1}),請下載後放入資料夾,下載網址: {2}</value> <value>在資料夾 ({0}) 中找不到 Core 檔案(檔名:{1})。請下載後放入該資料夾。下載網址:{2}</value>
</data> </data>
<data name="NoValidQRcodeFound" xml:space="preserve"> <data name="NoValidQRcodeFound" xml:space="preserve">
<value>掃描完成,未發現有效二維碼</value> <value>掃描完成,未發現有效二維碼</value>
@ -304,7 +304,7 @@
<value>是否確定移除規則?</value> <value>是否確定移除規則?</value>
</data> </data>
<data name="RoutingRuleDetailRequiredTips" xml:space="preserve"> <data name="RoutingRuleDetailRequiredTips" xml:space="preserve">
<value>{0}必填其中一項.</value> <value>{0}至少需填寫其中一項。</value>
</data> </data>
<data name="LvRemarks" xml:space="preserve"> <data name="LvRemarks" xml:space="preserve">
<value>別名</value> <value>別名</value>
@ -385,7 +385,7 @@
<value>所有</value> <value>所有</value>
</data> </data>
<data name="FillServerAddressCustom" xml:space="preserve"> <data name="FillServerAddressCustom" xml:space="preserve">
<value>請瀏覽匯入設定</value> <value>請選擇要匯入的設定檔</value>
</data> </data>
<data name="Speedtesting" xml:space="preserve"> <data name="Speedtesting" xml:space="preserve">
<value>測試中...</value> <value>測試中...</value>
@ -472,7 +472,7 @@
<value>語言 (需重啟)</value> <value>語言 (需重啟)</value>
</data> </data>
<data name="menuAddServerViaClipboard" xml:space="preserve"> <data name="menuAddServerViaClipboard" xml:space="preserve">
<value>從剪貼簿入分享連結</value> <value>從剪貼簿入分享連結</value>
</data> </data>
<data name="menuAddServerViaScan" xml:space="preserve"> <data name="menuAddServerViaScan" xml:space="preserve">
<value>掃描螢幕上的二維碼</value> <value>掃描螢幕上的二維碼</value>
@ -616,10 +616,10 @@
<value>SNI</value> <value>SNI</value>
</data> </data>
<data name="TbStreamSecurity" xml:space="preserve"> <data name="TbStreamSecurity" xml:space="preserve">
<value>傳輸層安全 (TLS)</value> <value>傳輸層安全 (TLS)</value>
</data> </data>
<data name="TipNetwork" xml:space="preserve"> <data name="TipNetwork" xml:space="preserve">
<value>*預設 TCP選錯會無法連</value> <value>*預設 TCP選錯會無法連</value>
</data> </data>
<data name="TbCoreType" xml:space="preserve"> <data name="TbCoreType" xml:space="preserve">
<value>Core 類型</value> <value>Core 類型</value>
@ -652,7 +652,7 @@
<value>SOCKS 埠</value> <value>SOCKS 埠</value>
</data> </data>
<data name="TipPreSocksPort" xml:space="preserve"> <data name="TipPreSocksPort" xml:space="preserve">
<value>*自訂設定的 Socks 埠值,可不設定;當設定此值後,將使用 Xray/sing-box (Tun) 額外啟動一個前置 Socks 服務,提供分流和速度顯示等功能</value> <value>*自訂設定的 Socks 埠值,可留空;當設定此值後,將使用 Xray/sing-box (Tun) 額外啟動一個前置 Socks 服務,提供分流和速度顯示等功能</value>
</data> </data>
<data name="TbBrowse" xml:space="preserve"> <data name="TbBrowse" xml:space="preserve">
<value>瀏覽</value> <value>瀏覽</value>
@ -1309,7 +1309,7 @@
<value>安裝字體到系統中,選擇或填入字體名稱,重新啟動後生效</value> <value>安裝字體到系統中,選擇或填入字體名稱,重新啟動後生效</value>
</data> </data>
<data name="menuExitTips" xml:space="preserve"> <data name="menuExitTips" xml:space="preserve">
<value>是否確定退出?</value> <value>確定退出</value>
</data> </data>
<data name="LvMemo" xml:space="preserve"> <data name="LvMemo" xml:space="preserve">
<value>備註備忘</value> <value>備註備忘</value>
@ -1368,24 +1368,6 @@
<data name="TbPorts7Tips" xml:space="preserve"> <data name="TbPorts7Tips" xml:space="preserve">
<value>會覆蓋埠,多組時用逗號 (,) 隔開</value> <value>會覆蓋埠,多組時用逗號 (,) 隔開</value>
</data> </data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>多選生成策略組</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<value>多選隨機 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多選負載平衡 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<value>多選最低延遲 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多選最穩定 Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多選最低延遲 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve"> <data name="menuExportConfig" xml:space="preserve">
<value>匯出</value> <value>匯出</value>
</data> </data>
@ -1513,13 +1495,13 @@
<value>策略組類型</value> <value>策略組類型</value>
</data> </data>
<data name="menuAddPolicyGroupServer" xml:space="preserve"> <data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>添加策略組</value> <value>新增策略組</value>
</data> </data>
<data name="menuAddProxyChainServer" xml:space="preserve"> <data name="menuAddProxyChainServer" xml:space="preserve">
<value>添加鏈式代理</value> <value>新增鏈式代理</value>
</data> </data>
<data name="menuAddChildServer" xml:space="preserve"> <data name="menuAddChildServer" xml:space="preserve">
<value>添加子配置</value> <value>新增子配置</value>
</data> </data>
<data name="menuRemoveChildServer" xml:space="preserve"> <data name="menuRemoveChildServer" xml:space="preserve">
<value>刪除子配置</value> <value>刪除子配置</value>
@ -1530,12 +1512,6 @@
<data name="TbFallback" xml:space="preserve"> <data name="TbFallback" xml:space="preserve">
<value>容錯移轉</value> <value>容錯移轉</value>
</data> </data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>多選容錯移轉 sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>多選容錯移轉 Xray</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve"> <data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支援網路類型 '{1}'</value> <value>核心 '{0}' 不支援網路類型 '{1}'</value>
</data> </data>
@ -1615,72 +1591,81 @@
<value>EchForceQuery</value> <value>EchForceQuery</value>
</data> </data>
<data name="TbFullCertTips" xml:space="preserve"> <data name="TbFullCertTips" xml:space="preserve">
<value>Full certificate (chain), PEM format</value> <value>完整憑證PEM 格式</value>
</data> </data>
<data name="TbCertSha256Tips" xml:space="preserve"> <data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value> <value>憑證指紋SHA-256</value>
</data> </data>
<data name="TbServeStale" xml:space="preserve"> <data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value> <value>提供過期快取(Serve Stale</value>
</data> </data>
<data name="TbParallelQuery" xml:space="preserve"> <data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value> <value>并行查詢</value>
</data> </data>
<data name="TbDomesticDNSTips" xml:space="preserve"> <data name="TbDomesticDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution</value> <value>預設僅在路由期間進行解析時調用</value>
</data> </data>
<data name="TbRemoteDNSTips" xml:space="preserve"> <data name="TbRemoteDNSTips" xml:space="preserve">
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value> <value>預設僅在路由期間進行解析時調用;請確保遠端伺服器能連線至此 DNS</value>
</data> </data>
<data name="TbDirectResolveStrategyTips" xml:space="preserve"> <data name="TbDirectResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value> <value>若未設定或為 "AsIs",使用系統 DNS 解析;否則將使用內建 DNS 模組。</value>
</data> </data>
<data name="TbRemoteResolveStrategyTips" xml:space="preserve"> <data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value> <value>若未設定或為 "AsIs",由遠端伺服器的 DNS 解析;否則將使用內建 DNS 模組。</value>
</data> </data>
<data name="TbHopInt7" xml:space="preserve"> <data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value> <value>連接埠跳轉間隔</value>
</data> </data>
<data name="menuServerListPreview" xml:space="preserve"> <data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value> <value>子配置項預覽</value>
</data> </data>
<data name="TbFinalmask" xml:space="preserve"> <data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value> <value>Finalmask</value>
</data> </data>
<data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve"> <data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve">
<value>Routing rule {0} outbound node {1} warning: {2}</value> <value>路由規則 {0} 的出站節點 {1} 發出警告:{2}</value>
</data> </data>
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve"> <data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
<value>Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only.</value> <value>路由規則 {0} 的出站節點 {1} 發生錯誤:{2}。已回退為僅使用代理節點。</value>
</data> </data>
<data name="MsgGroupCycleDependency" xml:space="preserve"> <data name="MsgGroupCycleDependency" xml:space="preserve">
<value>Group {0} has a cycle dependency on child node {1}. Skipping this node.</value> <value>節點組 {0} 與子節點 {1} 存在循環依賴。已跳過此節點。</value>
</data> </data>
<data name="MsgGroupChildNodeWarning" xml:space="preserve"> <data name="MsgGroupChildNodeWarning" xml:space="preserve">
<value>Group {0} child node {1} warning: {2}</value> <value>節點組 {0} 的子節點 {1} 發出警告:{2}</value>
</data> </data>
<data name="MsgGroupChildNodeError" xml:space="preserve"> <data name="MsgGroupChildNodeError" xml:space="preserve">
<value>Group {0} child node {1} error: {2}. Skipping this node.</value> <value>節點組 {0} 的子節點 {1} 發生錯誤:{2}。已跳過此節點。</value>
</data> </data>
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve"> <data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
<value>Group {0} child group node {1} warning: {2}</value> <value>節點組 {0} 的子節點組 {1} 發出警告:{2}</value>
</data> </data>
<data name="MsgGroupChildGroupNodeError" xml:space="preserve"> <data name="MsgGroupChildGroupNodeError" xml:space="preserve">
<value>Group {0} child group node {1} error: {2}. Skipping this node.</value> <value>節點組 {0} 的子節點組 {1} 發生錯誤:{2}。已跳過此節點。</value>
</data> </data>
<data name="MsgGroupNoValidChildNode" xml:space="preserve"> <data name="MsgGroupNoValidChildNode" xml:space="preserve">
<value>Group {0} has no valid child node.</value> <value>節點組 {0} 沒有可用的有效子節點。</value>
</data> </data>
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve"> <data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value> <value>路由規則 {0} 的出站標籤為空。已回退為僅使用代理節點。</value>
</data> </data>
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve"> <data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value> <value>找不到路由規則 {0} 的出站節點 {1}。已回退為僅使用代理節點。</value>
</data> </data>
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve"> <data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
<value>Subscription previous proxy {0} not found. Skipping.</value> <value>找不到訂閱的前一個代理 {0}。已跳過。</value>
</data> </data>
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve"> <data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Subscription next proxy {0} not found. Skipping.</value> <value>找不到訂閱的下一個代理 {0}。已跳過。</value>
</data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>生成策略組</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>所有配置項</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>按區域分組</value>
</data> </data>
</root> </root>

View file

@ -149,11 +149,11 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) if (!(Global.SingboxSupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType()))
{ {
continue; continue;
} }
if (it.Port <= 0) if (!it.ConfigType.IsComplexType() && it.Port <= 0)
{ {
continue; continue;
} }

View file

@ -148,15 +148,20 @@ public partial class CoreConfigSingboxService
_coreConfig.dns ??= new Dns4Sbox(); _coreConfig.dns ??= new Dns4Sbox();
_coreConfig.dns.rules ??= []; _coreConfig.dns.rules ??= [];
_coreConfig.dns.rules.AddRange(new[] _coreConfig.dns.rules.Add(new() { ip_accept_any = true, server = Global.SingboxHostsDNSTag });
if (context.ProtectDomainList.Count > 0)
{ {
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag }, _coreConfig.dns.rules.Add(new()
new Rule4Sbox
{ {
server = Global.SingboxDirectDNSTag, server = Global.SingboxDirectDNSTag,
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom), strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
domain = context.ProtectDomainList.ToList(), domain = context.ProtectDomainList.ToList(),
}, });
}
_coreConfig.dns.rules.AddRange(new[]
{
new Rule4Sbox new Rule4Sbox
{ {
server = Global.SingboxRemoteDNSTag, server = Global.SingboxRemoteDNSTag,
@ -428,7 +433,11 @@ public partial class CoreConfigSingboxService
localDnsServer.tag = tag; localDnsServer.tag = tag;
dns4Sbox.servers.Add(localDnsServer); dns4Sbox.servers.Add(localDnsServer);
dns4Sbox.rules.Insert(0, BuildProtectDomainRule()); var protectDomainRule = BuildProtectDomainRule();
if (protectDomainRule != null)
{
dns4Sbox.rules.Insert(0, protectDomainRule);
}
_coreConfig.dns = dns4Sbox; _coreConfig.dns = dns4Sbox;
} }
@ -450,8 +459,12 @@ public partial class CoreConfigSingboxService
_coreConfig.dns?.servers?.Add(localDnsServer); _coreConfig.dns?.servers?.Add(localDnsServer);
} }
private Rule4Sbox BuildProtectDomainRule() private Rule4Sbox? BuildProtectDomainRule()
{ {
if (context.ProtectDomainList.Count == 0)
{
return null;
}
return new() return new()
{ {
server = Global.SingboxLocalDNSTag, server = Global.SingboxLocalDNSTag,

View file

@ -112,11 +112,11 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
foreach (var it in selecteds) foreach (var it in selecteds)
{ {
if (!Global.XraySupportConfigType.Contains(it.ConfigType)) if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType()))
{ {
continue; continue;
} }
if (it.Port <= 0) if (!it.ConfigType.IsComplexType() && it.Port <= 0)
{ {
continue; continue;
} }
@ -180,13 +180,13 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
//rule //rule
RulesItem4Ray rule = new() RulesItem4Ray rule = new()
{ {
inboundTag = new List<string> { inbound.tag }, inboundTag = [inbound.tag],
outboundTag = tag, outboundTag = tag,
type = "field" type = "field"
}; };
if (isBalancer) if (isBalancer)
{ {
rule.balancerTag = tag; rule.balancerTag = tag + Global.BalancerTagSuffix;
rule.outboundTag = null; rule.outboundTag = null;
} }
_coreConfig.routing.rules.Add(rule); _coreConfig.routing.rules.Add(rule);
@ -301,6 +301,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
GenLog(); GenLog();
_coreConfig.outbounds.Clear(); _coreConfig.outbounds.Clear();
GenOutbounds(); GenOutbounds();
GenStatistic();
var protectNode = new ProfileItem() var protectNode = new ProfileItem()
{ {
@ -326,18 +327,17 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
Node = protectNode, Node = protectNode,
}).BuildProxyOutbound("tun-project-ss")); }).BuildProxyOutbound("tun-project-ss"));
_coreConfig.routing.rules ??= [];
var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 }; var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 };
_coreConfig.routing.rules = _coreConfig.routing.rules.Add(new()
[ {
new() inboundTag = ["proxy-relay-ss"],
{ outboundTag = hasBalancer ? null : Global.ProxyTag,
inboundTag = new List<string> { "proxy-relay-ss" }, balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix : null,
outboundTag = hasBalancer ? null : Global.ProxyTag, type = "field"
balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix: null, });
type = "field"
} //_coreConfig.inbounds.Clear();
];
_coreConfig.inbounds.Clear();
var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!; var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!;
configNode["inbounds"]!.AsArray().Add(new configNode["inbounds"]!.AsArray().Add(new

View file

@ -71,6 +71,25 @@ public partial class CoreConfigV2rayService
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null; dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
// DNS routing // DNS routing
var directDnsTags = dnsItem.servers
.Select(server =>
{
var tagNode = (server as JsonObject)?["tag"];
return tagNode is JsonValue value && value.TryGetValue<string>(out var tag) ? tag : null;
})
.Where(tag => tag is not null && tag.StartsWith(Global.DirectDnsTag, StringComparison.Ordinal))
.Select(tag => tag!)
.ToList();
if (directDnsTags.Count > 0)
{
_coreConfig.routing.rules.Add(new()
{
type = "field",
inboundTag = directDnsTags,
outboundTag = Global.DirectTag,
});
}
var finalRule = BuildFinalRule(); var finalRule = BuildFinalRule();
dnsItem.tag = Global.DnsTag; dnsItem.tag = Global.DnsTag;
_coreConfig.routing.rules.Add(new() _coreConfig.routing.rules.Add(new()
@ -78,7 +97,7 @@ public partial class CoreConfigV2rayService
type = "field", type = "field",
inboundTag = [Global.DnsTag], inboundTag = [Global.DnsTag],
outboundTag = finalRule.outboundTag, outboundTag = finalRule.outboundTag,
balancerTag = finalRule.balancerTag balancerTag = finalRule.balancerTag,
}); });
_coreConfig.dns = dnsItem; _coreConfig.dns = dnsItem;
@ -212,11 +231,13 @@ public partial class CoreConfigV2rayService
dnsItem.servers ??= []; dnsItem.servers ??= [];
var directDnsTagIndex = 1;
AddDnsServers(remoteDNSAddress, proxyDomainList); AddDnsServers(remoteDNSAddress, proxyDomainList);
AddDnsServers(directDNSAddress, directDomainList); AddDnsServers(directDNSAddress, directDomainList, true);
AddDnsServers(remoteDNSAddress, proxyGeositeList); AddDnsServers(remoteDNSAddress, proxyGeositeList);
AddDnsServers(directDNSAddress, directGeositeList); AddDnsServers(directDNSAddress, directGeositeList, true);
AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs); AddDnsServers(directDNSAddress, expectedDomainList, true, expectedIPs);
if (dnsServerDomains.Count > 0) if (dnsServerDomains.Count > 0)
{ {
AddDnsServers(bootstrapDNSAddress, dnsServerDomains); AddDnsServers(bootstrapDNSAddress, dnsServerDomains);
@ -234,8 +255,21 @@ public partial class CoreConfigV2rayService
useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork; useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork;
} }
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress; if (!useDirectDns)
dnsItem.servers.AddRange(defaultDnsServers); {
dnsItem.servers.AddRange(remoteDNSAddress);
}
else
{
foreach (var dns in directDNSAddress)
{
var dnsServer = CreateDnsServer(dns, []);
dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}";
dnsServer.skipFallback = false;
dnsItem.servers.Add(JsonUtils.SerializeToNode(dnsServer,
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }));
}
}
return; return;
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress) static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
@ -249,7 +283,7 @@ public partial class CoreConfigV2rayService
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress }; return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
} }
static object? CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null) static DnsServer4Ray CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
{ {
var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress); var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress);
var domainFinal = dnsAddress; var domainFinal = dnsAddress;
@ -272,13 +306,10 @@ public partial class CoreConfigV2rayService
domains = domains.Count > 0 ? domains : null, domains = domains.Count > 0 ? domains : null,
expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null
}; };
return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions return dnsServer;
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
} }
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null) void AddDnsServers(List<string> dnsAddresses, List<string> domains, bool isDirectDns = false, List<string>? expectedIPs = null)
{ {
if (domains.Count <= 0) if (domains.Count <= 0)
{ {
@ -286,7 +317,14 @@ public partial class CoreConfigV2rayService
} }
foreach (var dnsAddress in dnsAddresses) foreach (var dnsAddress in dnsAddresses)
{ {
dnsItem.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs)); var dnsServer = CreateDnsServer(dnsAddress, domains, expectedIPs);
if (isDirectDns)
{
dnsServer.tag = $"{Global.DirectDnsTag}-{directDnsTagIndex++}";
}
var dnsServerNode = JsonUtils.SerializeToNode(dnsServer,
new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
dnsItem.servers.Add(dnsServerNode);
} }
} }
} }
@ -399,7 +437,7 @@ public partial class CoreConfigV2rayService
FillDnsDomainsCustom(obj); FillDnsDomainsCustom(obj);
_coreConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj)); _coreConfig.dns = obj;
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -67,19 +67,27 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private async Task<List<ServerTestItem>> GetClearItem(ESpeedActionType actionType, List<ProfileItem> selecteds) private async Task<List<ServerTestItem>> GetClearItem(ESpeedActionType actionType, List<ProfileItem> selecteds)
{ {
var lstSelected = new List<ServerTestItem>(); var lstSelected = new List<ServerTestItem>(selecteds.Count);
foreach (var it in selecteds) var ids = selecteds.Where(it => !it.IndexId.IsNullOrEmpty()
&& it.ConfigType != EConfigType.Custom
&& (it.ConfigType.IsComplexType() || it.Port > 0))
.Select(it => it.IndexId)
.ToList();
var profileMap = await AppManager.Instance.GetProfileItemsByIndexIdsAsMap(ids);
for (var i = 0; i < selecteds.Count; i++)
{ {
if (it.ConfigType.IsComplexType()) var it = selecteds[i];
if (it.ConfigType == EConfigType.Custom)
{ {
continue; continue;
} }
if (it.Port <= 0) if (!it.ConfigType.IsComplexType() && it.Port <= 0)
{ {
continue; continue;
} }
var profile = profileMap.GetValueOrDefault(it.IndexId, it);
lstSelected.Add(new ServerTestItem() lstSelected.Add(new ServerTestItem()
{ {
IndexId = it.IndexId, IndexId = it.IndexId,
@ -88,6 +96,9 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
ConfigType = it.ConfigType, ConfigType = it.ConfigType,
NeedAutoFillRemarks = it.Remarks.IsNullOrEmpty(), NeedAutoFillRemarks = it.Remarks.IsNullOrEmpty(),
QueueNum = selecteds.IndexOf(it) QueueNum = selecteds.IndexOf(it)
QueueNum = i,
Profile = profile,
CoreType = AppManager.Instance.GetCoreType(profile, it.ConfigType),
}); });
} }
@ -397,8 +408,8 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize) private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
{ {
List<List<ServerTestItem>> lstTest = new(); List<List<ServerTestItem>> lstTest = new();
var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList(); var lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList();
var lst2 = lstSelected.Where(t => Global.SingboxOnlyConfigType.Contains(t.ConfigType)).ToList(); var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList();
for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++) for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
{ {

View file

@ -99,15 +99,8 @@ public class AddGroupServerViewModel : MyReactiveObject
Filter = protocolExtra?.Filter; Filter = protocolExtra?.Filter;
var childIndexIds = Utils.String2List(protocolExtra?.ChildItems) ?? []; var childIndexIds = Utils.String2List(protocolExtra?.ChildItems) ?? [];
foreach (var item in childIndexIds) var childItemList = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(childIndexIds);
{ ChildItemsObs.AddRange(childItemList);
var child = await AppManager.Instance.GetProfileItem(item);
if (child == null)
{
continue;
}
ChildItemsObs.Add(child);
}
} }
public async Task ChildRemoveAsync() public async Task ChildRemoveAsync()

View file

@ -546,24 +546,15 @@ public class MainWindowViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings); NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings);
return; return;
} }
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, profileItem); var allResult = await CoreConfigContextBuilder.BuildAll(_config, profileItem);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]); if (NoticeManager.Instance.NotifyValidatorResult(allResult.CombinedValidatorResult) && !allResult.Success)
if (msgs.Count > 0)
{ {
foreach (var msg in msgs) return;
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
} }
await Task.Run(async () => await Task.Run(async () =>
{ {
await LoadCore(context); await LoadCore(allResult.ResolvedMainContext, allResult.PreSocksResult?.Context);
await SysProxyHandler.UpdateSysProxy(_config, false); await SysProxyHandler.UpdateSysProxy(_config, false);
await Task.Delay(1000); await Task.Delay(1000);
}); });
@ -604,9 +595,9 @@ public class MainWindowViewModel : MyReactiveObject
RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled); RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled);
} }
private async Task LoadCore(CoreConfigContext? context) private async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{ {
await CoreManager.Instance.LoadCore(context); await CoreManager.Instance.LoadCore(mainContext, preContext);
} }
#endregion core job #endregion core job

View file

@ -255,19 +255,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
{ {
return null; return null;
} }
var lst = new List<ProfileItem>(); var lst = await AppManager.Instance.GetProfileItemsOrderedByIndexIds(SelectedProfiles.Select(sp => sp?.IndexId));
foreach (var sp in SelectedProfiles)
{
if (string.IsNullOrEmpty(sp?.IndexId))
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(sp.IndexId);
if (item != null)
{
lst.Add(item);
}
}
if (lst.Count == 0) if (lst.Count == 0)
{ {
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer); NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);

View file

@ -8,6 +8,7 @@ public class ProfilesViewModel : MyReactiveObject
private string _serverFilter = string.Empty; private string _serverFilter = string.Empty;
private Dictionary<string, bool> _dicHeaderSort = new(); private Dictionary<string, bool> _dicHeaderSort = new();
private SpeedtestService? _speedtestService; private SpeedtestService? _speedtestService;
private string? _pendingSelectIndexId;
#endregion private prop #endregion private prop
@ -43,13 +44,8 @@ public class ProfilesViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> CopyServerCmd { get; } public ReactiveCommand<Unit, Unit> CopyServerCmd { get; }
public ReactiveCommand<Unit, Unit> SetDefaultServerCmd { get; } public ReactiveCommand<Unit, Unit> SetDefaultServerCmd { get; }
public ReactiveCommand<Unit, Unit> ShareServerCmd { get; } public ReactiveCommand<Unit, Unit> ShareServerCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayRandomCmd { get; } public ReactiveCommand<Unit, Unit> GenGroupAllServerCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayRoundRobinCmd { get; } public ReactiveCommand<Unit, Unit> GenGroupRegionServerCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayLeastPingCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayLeastLoadCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayFallbackCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerSingBoxLeastPingCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerSingBoxFallbackCmd { get; }
//servers move //servers move
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; } public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
@ -134,33 +130,13 @@ public class ProfilesViewModel : MyReactiveObject
{ {
await ShareServerAsync(); await ShareServerAsync();
}, canEditRemove); }, canEditRemove);
GenGroupMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () => GenGroupAllServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Random); await GenGroupAllServer();
}, canEditRemove); }, canEditRemove);
GenGroupMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () => GenGroupRegionServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin); await GenGroupRegionServer();
}, canEditRemove);
GenGroupMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing);
}, canEditRemove);
GenGroupMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad);
}, canEditRemove);
GenGroupMultipleServerXrayFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Fallback);
}, canEditRemove);
GenGroupMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing);
}, canEditRemove);
GenGroupMultipleServerSingBoxFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.Fallback);
}, canEditRemove); }, canEditRemove);
//servers move //servers move
@ -392,15 +368,14 @@ public class ProfilesViewModel : MyReactiveObject
ProfileItems.AddRange(lstModel); ProfileItems.AddRange(lstModel);
if (lstModel.Count > 0) if (lstModel.Count > 0)
{ {
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId); ProfileItemModel? selected = null;
if (selected != null) if (!_pendingSelectIndexId.IsNullOrEmpty())
{ {
SelectedProfile = selected; selected = lstModel.FirstOrDefault(t => t.IndexId == _pendingSelectIndexId);
} _pendingSelectIndexId = null;
else
{
SelectedProfile = lstModel.First();
} }
selected ??= lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
SelectedProfile = selected ?? lstModel.First();
} }
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null); await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
@ -481,14 +456,7 @@ public class ProfilesViewModel : MyReactiveObject
var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort); var orderProfiles = SelectedProfiles?.OrderBy(t => t.Sort);
if (latest) if (latest)
{ {
foreach (var profile in orderProfiles) lstSelected.AddRange(await AppManager.Instance.GetProfileItemsOrderedByIndexIds(orderProfiles.Select(sp => sp?.IndexId)));
{
var item = await AppManager.Instance.GetProfileItem(profile.IndexId);
if (item is not null)
{
lstSelected.Add(item);
}
}
} }
else else
{ {
@ -641,29 +609,29 @@ public class ProfilesViewModel : MyReactiveObject
await _updateView?.Invoke(EViewAction.ShareServer, url); await _updateView?.Invoke(EViewAction.ShareServer, url);
} }
private async Task GenGroupMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad) private async Task GenGroupAllServer()
{ {
var lstSelected = await GetProfileItems(true); var ret = await ConfigHandler.AddGroupAllServer(_config, SelectedSub);
if (lstSelected == null)
{
return;
}
var ret = await ConfigHandler.AddGroupServer4Multiple(_config, lstSelected, coreType, multipleLoad, SelectedSub?.Id);
if (ret.Success != true) if (ret.Success != true)
{ {
NoticeManager.Instance.Enqueue(ResUI.OperationFailed); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
return; return;
} }
if (ret?.Data?.ToString() == _config.IndexId) _pendingSelectIndexId = ret.Data?.ToString();
await RefreshServers();
}
private async Task GenGroupRegionServer()
{
var ret = await ConfigHandler.AddGroupRegionServer(_config, SelectedSub);
if (ret.Success != true)
{ {
await RefreshServers(); NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
Reload(); return;
}
else
{
await SetDefaultServer(ret?.Data?.ToString());
} }
var indexIdList = ret.Data as List<string>;
_pendingSelectIndexId = indexIdList?.FirstOrDefault();
await RefreshServers();
} }
public async Task SortServer(string colName) public async Task SortServer(string colName)
@ -789,18 +757,9 @@ public class ProfilesViewModel : MyReactiveObject
} }
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item); var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]); if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
if (msgs.Count > 0)
{ {
foreach (var msg in msgs) return;
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
} }
if (blClipboard) if (blClipboard)
@ -829,18 +788,9 @@ public class ProfilesViewModel : MyReactiveObject
return; return;
} }
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item); var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]); if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
if (msgs.Count > 0)
{ {
foreach (var msg in msgs) return;
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
} }
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName); var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true) if (result.Success != true)

View file

@ -95,7 +95,7 @@
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}"> <TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid <Grid
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
ColumnDefinitions="300,Auto,Auto" ColumnDefinitions="Auto,Auto,Auto"
RowDefinitions="Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto">
<TextBlock <TextBlock
@ -108,7 +108,7 @@
x:Name="cmbSubChildItems" x:Name="cmbSubChildItems"
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
Width="200" Width="600"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
DisplayMemberBinding="{Binding Remarks}" DisplayMemberBinding="{Binding Remarks}"
ItemsSource="{Binding SubItems}" /> ItemsSource="{Binding SubItems}" />
@ -117,7 +117,8 @@
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" /> Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}"
TextWrapping="Wrap" />
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -125,12 +126,14 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.LvFilter}" /> Text="{x:Static resx:ResUI.LvFilter}" />
<TextBox <ComboBox
x:Name="txtFilter" x:Name="cmbFilter"
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Width="600"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" /> VerticalAlignment="Center"
IsEditable="True" />
</Grid> </Grid>
</TabItem> </TabItem>
@ -184,11 +187,11 @@
Binding="{Binding ConfigType}" Binding="{Binding ConfigType}"
Header="{x:Static resx:ResUI.LvServiceType}" /> Header="{x:Static resx:ResUI.LvServiceType}" />
<DataGridTextColumn <DataGridTextColumn
Width="150" Width="200"
Binding="{Binding Remarks}" Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" /> Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn <DataGridTextColumn
Width="120" Width="200"
Binding="{Binding Address}" Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}" /> Header="{x:Static resx:ResUI.LvAddress}" />
<DataGridTextColumn <DataGridTextColumn
@ -226,11 +229,11 @@
Binding="{Binding ConfigType}" Binding="{Binding ConfigType}"
Header="{x:Static resx:ResUI.LvServiceType}" /> Header="{x:Static resx:ResUI.LvServiceType}" />
<DataGridTextColumn <DataGridTextColumn
Width="150" Width="200"
Binding="{Binding Remarks}" Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" /> Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn <DataGridTextColumn
Width="120" Width="200"
Binding="{Binding Address}" Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}" /> Header="{x:Static resx:ResUI.LvAddress}" />
<DataGridTextColumn <DataGridTextColumn

View file

@ -29,6 +29,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
ResUI.TbRoundRobin, ResUI.TbRoundRobin,
ResUI.TbLeastLoad, ResUI.TbLeastLoad,
}; };
cmbFilter.ItemsSource = Global.PolicyGroupDefaultFilterList;
switch (profileItem.ConfigType) switch (profileItem.ConfigType)
{ {
@ -53,7 +54,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables);
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables); //this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
@ -147,14 +148,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
private async void MenuAddChild_Click(object? sender, RoutedEventArgs e) private async void MenuAddChild_Click(object? sender, RoutedEventArgs e)
{ {
var selectWindow = new ProfilesSelectWindow(); var selectWindow = new ProfilesSelectWindow();
if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup) selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
}
else
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
}
selectWindow.AllowMultiSelect(true); selectWindow.AllowMultiSelect(true);
var result = await selectWindow.ShowDialog<bool?>(this); var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true) if (result == true)

View file

@ -90,17 +90,17 @@
Header="{x:Static resx:ResUI.LvServiceType}" Header="{x:Static resx:ResUI.LvServiceType}"
Tag="ConfigType" /> Tag="ConfigType" />
<DataGridTextColumn <DataGridTextColumn
Width="120" Width="200"
Binding="{Binding Remarks}" Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" Header="{x:Static resx:ResUI.LvRemarks}"
Tag="Remarks" /> Tag="Remarks" />
<DataGridTextColumn <DataGridTextColumn
Width="120" Width="200"
Binding="{Binding Address}" Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}" Header="{x:Static resx:ResUI.LvAddress}"
Tag="Address" /> Tag="Address" />
<DataGridTextColumn <DataGridTextColumn
Width="60" Width="100"
Binding="{Binding Port}" Binding="{Binding Port}"
Header="{x:Static resx:ResUI.LvPort}" Header="{x:Static resx:ResUI.LvPort}"
Tag="Port" /> Tag="Port" />

View file

@ -193,14 +193,9 @@
<MenuItem x:Name="menuExport2ShareUrlBase64" Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" /> <MenuItem x:Name="menuExport2ShareUrlBase64" Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" />
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupMultipleServer}"> <MenuItem Header="{x:Static resx:ResUI.menuGenGroupServer}">
<MenuItem x:Name="menuGenGroupMultipleServerXrayRandom" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRandom}" /> <MenuItem x:Name="menuGenGroupAllServer" Header="{x:Static resx:ResUI.menuAllServers}" />
<MenuItem x:Name="menuGenGroupMultipleServerXrayRoundRobin" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRoundRobin}" /> <MenuItem x:Name="menuGenGroupRegionServer" Header="{x:Static resx:ResUI.menuGenRegionGroup}" />
<MenuItem x:Name="menuGenGroupMultipleServerXrayLeastPing" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastPing}" />
<MenuItem x:Name="menuGenGroupMultipleServerXrayLeastLoad" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastLoad}" />
<Separator />
<MenuItem x:Name="menuGenGroupMultipleServerSingBoxLeastPing" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" />
<MenuItem x:Name="menuGenGroupMultipleServerSingBoxFallback" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxFallback}" />
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>

View file

@ -60,12 +60,8 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRandomCmd, v => v.menuGenGroupMultipleServerXrayRandom).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRoundRobinCmd, v => v.menuGenGroupMultipleServerXrayRoundRobin).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupRegionServerCmd, v => v.menuGenGroupRegionServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables);
//servers move //servers move
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); //this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);

View file

@ -59,7 +59,7 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e) private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e)
{ {
var selectWindow = new ProfilesSelectWindow(); var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
var result = await selectWindow.ShowDialog<bool?>(this); var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true) if (result == true)
{ {
@ -74,7 +74,7 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
private async void BtnSelectNextProfile_Click(object? sender, RoutedEventArgs e) private async void BtnSelectNextProfile_Click(object? sender, RoutedEventArgs e)
{ {
var selectWindow = new ProfilesSelectWindow(); var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
var result = await selectWindow.ShowDialog<bool?>(this); var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true) if (result == true)
{ {

View file

@ -147,7 +147,7 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="300" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@ -163,7 +163,7 @@
x:Name="cmbSubChildItems" x:Name="cmbSubChildItems"
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
Width="200" Width="600"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
DisplayMemberPath="Remarks" DisplayMemberPath="Remarks"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
@ -173,7 +173,8 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}" /> Text="{x:Static resx:ResUI.TbPolicyGroupSubChildTip}"
TextWrapping="Wrap" />
<TextBlock <TextBlock
Grid.Row="1" Grid.Row="1"
@ -182,14 +183,15 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.LvFilter}" /> Text="{x:Static resx:ResUI.LvFilter}" />
<TextBox <ComboBox
x:Name="txtFilter" x:Name="cmbFilter"
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Width="600"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
AcceptsReturn="True" IsEditable="True"
Style="{StaticResource DefTextBox}" /> Style="{StaticResource DefComboBox}" />
</Grid> </Grid>
</TabItem> </TabItem>
@ -251,11 +253,11 @@
Binding="{Binding ConfigType}" Binding="{Binding ConfigType}"
Header="{x:Static resx:ResUI.LvServiceType}" /> Header="{x:Static resx:ResUI.LvServiceType}" />
<DataGridTextColumn <DataGridTextColumn
Width="150" Width="200"
Binding="{Binding Remarks}" Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" /> Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn <DataGridTextColumn
Width="120" Width="200"
Binding="{Binding Address}" Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}" /> Header="{x:Static resx:ResUI.LvAddress}" />
<DataGridTextColumn <DataGridTextColumn
@ -292,11 +294,11 @@
Binding="{Binding ConfigType}" Binding="{Binding ConfigType}"
Header="{x:Static resx:ResUI.LvServiceType}" /> Header="{x:Static resx:ResUI.LvServiceType}" />
<DataGridTextColumn <DataGridTextColumn
Width="150" Width="200"
Binding="{Binding Remarks}" Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}" /> Header="{x:Static resx:ResUI.LvRemarks}" />
<DataGridTextColumn <DataGridTextColumn
Width="120" Width="200"
Binding="{Binding Address}" Binding="{Binding Address}"
Header="{x:Static resx:ResUI.LvAddress}" /> Header="{x:Static resx:ResUI.LvAddress}" />
<DataGridTextColumn <DataGridTextColumn

View file

@ -24,6 +24,7 @@ public partial class AddGroupServerWindow
ResUI.TbRoundRobin, ResUI.TbRoundRobin,
ResUI.TbLeastLoad, ResUI.TbLeastLoad,
}; };
cmbFilter.ItemsSource = Global.PolicyGroupDefaultFilterList;
switch (profileItem.ConfigType) switch (profileItem.ConfigType)
{ {
@ -48,7 +49,7 @@ public partial class AddGroupServerWindow
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.Text).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.ChildItemsObs, v => v.lstChild.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
@ -127,14 +128,7 @@ public partial class AddGroupServerWindow
private async void MenuAddChild_Click(object sender, RoutedEventArgs e) private async void MenuAddChild_Click(object sender, RoutedEventArgs e)
{ {
var selectWindow = new ProfilesSelectWindow(); var selectWindow = new ProfilesSelectWindow();
if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup) selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
}
else
{
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
}
selectWindow.AllowMultiSelect(true); selectWindow.AllowMultiSelect(true);
if (selectWindow.ShowDialog() == true) if (selectWindow.ShowDialog() == true)
{ {

View file

@ -31,13 +31,13 @@ public partial class DNSSettingWindow
this.Bind(ViewModel, vm => vm.AddCommonHosts, v => v.togAddCommonHosts.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AddCommonHosts, v => v.togAddCommonHosts.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Freedom, v => v.cmbDirectDNSStrategy.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Strategy4Freedom, v => v.cmbDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ParallelQuery, v => v.togParallelQuery.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.ParallelQuery, v => v.togParallelQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServeStale, v => v.togServeStale.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.ServeStale, v => v.togServeStale.IsChecked).DisposeWith(disposables);

View file

@ -119,17 +119,17 @@
ExName="ConfigType" ExName="ConfigType"
Header="{x:Static resx:ResUI.LvServiceType}" /> Header="{x:Static resx:ResUI.LvServiceType}" />
<base:MyDGTextColumn <base:MyDGTextColumn
Width="150" Width="200"
Binding="{Binding Remarks}" Binding="{Binding Remarks}"
ExName="Remarks" ExName="Remarks"
Header="{x:Static resx:ResUI.LvRemarks}" /> Header="{x:Static resx:ResUI.LvRemarks}" />
<base:MyDGTextColumn <base:MyDGTextColumn
Width="120" Width="200"
Binding="{Binding Address}" Binding="{Binding Address}"
ExName="Address" ExName="Address"
Header="{x:Static resx:ResUI.LvAddress}" /> Header="{x:Static resx:ResUI.LvAddress}" />
<base:MyDGTextColumn <base:MyDGTextColumn
Width="60" Width="100"
Binding="{Binding Port}" Binding="{Binding Port}"
ExName="Port" ExName="Port"
Header="{x:Static resx:ResUI.LvPort}" /> Header="{x:Static resx:ResUI.LvPort}" />

View file

@ -240,32 +240,15 @@
Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" /> Header="{x:Static resx:ResUI.menuExport2ShareUrlBase64}" />
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupMultipleServer}"> <MenuItem Header="{x:Static resx:ResUI.menuGenGroupServer}">
<MenuItem <MenuItem
x:Name="menuGenGroupMultipleServerXrayRandom" x:Name="menuGenGroupAllServer"
Height="{StaticResource MenuItemHeight}" Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRandom}" /> Header="{x:Static resx:ResUI.menuAllServers}" />
<MenuItem <MenuItem
x:Name="menuGenGroupMultipleServerXrayRoundRobin" x:Name="menuGenGroupRegionServer"
Height="{StaticResource MenuItemHeight}" Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRoundRobin}" /> Header="{x:Static resx:ResUI.menuGenRegionGroup}" />
<MenuItem
x:Name="menuGenGroupMultipleServerXrayLeastPing"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastPing}" />
<MenuItem
x:Name="menuGenGroupMultipleServerXrayLeastLoad"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastLoad}" />
<Separator />
<MenuItem
x:Name="menuGenGroupMultipleServerSingBoxLeastPing"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" />
<MenuItem
x:Name="menuGenGroupMultipleServerSingBoxFallback"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxFallback}" />
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>

View file

@ -54,12 +54,8 @@ public partial class ProfilesView
this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRandomCmd, v => v.menuGenGroupMultipleServerXrayRandom).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRoundRobinCmd, v => v.menuGenGroupMultipleServerXrayRoundRobin).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupRegionServerCmd, v => v.menuGenGroupRegionServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables);
//servers move //servers move
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);

View file

@ -53,7 +53,7 @@ public partial class SubEditWindow
private async void BtnSelectPrevProfile_Click(object sender, RoutedEventArgs e) private async void BtnSelectPrevProfile_Click(object sender, RoutedEventArgs e)
{ {
var selectWindow = new ProfilesSelectWindow(); var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
if (selectWindow.ShowDialog() == true) if (selectWindow.ShowDialog() == true)
{ {
var profile = await selectWindow.ProfileItem; var profile = await selectWindow.ProfileItem;
@ -67,7 +67,7 @@ public partial class SubEditWindow
private async void BtnSelectNextProfile_Click(object sender, RoutedEventArgs e) private async void BtnSelectNextProfile_Click(object sender, RoutedEventArgs e)
{ {
var selectWindow = new ProfilesSelectWindow(); var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
if (selectWindow.ShowDialog() == true) if (selectWindow.ShowDialog() == true)
{ {
var profile = await selectWindow.ProfileItem; var profile = await selectWindow.ProfileItem;