This commit is contained in:
DHR60 2025-12-25 16:35:55 +08:00
parent fdb733fa72
commit a6b12aa718
18 changed files with 388 additions and 496 deletions

View file

@ -258,6 +258,7 @@ public static class ConfigHandler
item.CertSha = profileItem.CertSha;
item.EchConfigList = profileItem.EchConfigList;
item.EchForceQuery = profileItem.EchForceQuery;
item.JsonData = profileItem.JsonData;
}
var ret = item.ConfigType switch
@ -360,11 +361,6 @@ public static class ConfigHandler
{
}
}
else if (profileItem.ConfigType.IsGroupType())
{
var profileGroupItem = await AppManager.Instance.GetProfileGroupItem(it.IndexId);
await AddGroupServerCommon(config, profileItem, profileGroupItem, true);
}
else
{
await AddServerCommon(config, profileItem, true);
@ -967,9 +963,11 @@ public static class ConfigHandler
profileItem.Path = profileItem.Path.TrimEx();
profileItem.StreamSecurity = profileItem.StreamSecurity.TrimEx();
if (!Global.Flows.Contains(profileItem.Flow))
var extraItem = profileItem.GetExtraItem();
if (!Global.Flows.Contains(extraItem.Flow ?? string.Empty))
{
profileItem.Flow = Global.Flows.First();
extraItem.Flow = Global.Flows.First();
}
if (profileItem.Id.IsNullOrEmpty())
{
@ -980,6 +978,8 @@ public static class ConfigHandler
profileItem.Security = Global.None;
}
profileItem.SetExtraItem(extraItem);
await AddServerCommon(config, profileItem, toFile);
return 0;
@ -1082,37 +1082,6 @@ public static class ConfigHandler
return 0;
}
public static async Task<int> AddGroupServerCommon(Config config, ProfileItem profileItem, ProfileGroupItem profileGroupItem, bool toFile = true)
{
var maxSort = -1;
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.IndexId = Utils.GetGuid(false);
maxSort = ProfileExManager.Instance.GetMaxSort();
}
var groupType = profileItem.ConfigType == EConfigType.ProxyChain ? EConfigType.ProxyChain.ToString() : profileGroupItem.MultipleLoad.ToString();
profileItem.Address = $"{profileItem.CoreType}-{groupType}";
if (maxSort > 0)
{
ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
}
if (toFile)
{
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
if (profileGroupItem != null)
{
profileGroupItem.IndexId = profileItem.IndexId;
await ProfileGroupItemManager.Instance.SaveItemAsync(profileGroupItem);
}
else
{
ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(profileItem.IndexId);
await ProfileGroupItemManager.Instance.SaveTo();
}
}
return 0;
}
/// <summary>
/// Compare two profile items to determine if they represent the same server
/// Used for deduplication and server matching
@ -1128,6 +1097,8 @@ public static class ConfigHandler
return false;
}
var extraItem = o.GetExtraItem();
return o.ConfigType == n.ConfigType
&& AreEqual(o.Address, n.Address)
&& o.Port == n.Port
@ -1138,7 +1109,7 @@ public static class ConfigHandler
&& AreEqual(o.RequestHost, n.RequestHost)
&& AreEqual(o.Path, n.Path)
&& (o.ConfigType == EConfigType.Trojan || o.StreamSecurity == n.StreamSecurity)
&& AreEqual(o.Flow, n.Flow)
&& AreEqual(extraItem.Flow, extraItem.Flow)
&& AreEqual(o.Sni, n.Sni)
&& AreEqual(o.Alpn, n.Alpn)
&& AreEqual(o.Fingerprint, n.Fingerprint)
@ -1199,7 +1170,7 @@ public static class ConfigHandler
var indexId = Utils.GetGuid(false);
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId)).Remarks} ";
var remark = subId.IsNullOrEmpty() ? string.Empty : $"{(await AppManager.Instance.GetSubItem(subId))?.Remarks} ";
if (coreType == ECoreType.Xray)
{
remark += multipleLoad switch
@ -1233,13 +1204,12 @@ public static class ConfigHandler
{
profile.Subid = subId;
}
var profileGroup = new ProfileGroupItem
var extraItem = new ProtocolExtraItem
{
ChildItems = childProfileIndexId,
MultipleLoad = multipleLoad,
IndexId = indexId,
ChildItems = childProfileIndexId, MultipleLoad = multipleLoad,
};
var ret = await AddGroupServerCommon(config, profile, profileGroup, true);
profile.SetExtraItem(extraItem);
var ret = await AddServerCommon(config, profile, true);
result.Success = ret == 0;
result.Data = indexId;
return result;
@ -1261,7 +1231,7 @@ public static class ConfigHandler
var tun2SocksAddress = node.Address;
if (node.ConfigType.IsGroupType())
{
var lstAddresses = (await ProfileGroupItemManager.GetAllChildDomainAddresses(node.IndexId)).ToList();
var lstAddresses = (await GroupProfileManager.GetAllChildDomainAddresses(node)).ToList();
if (lstAddresses.Count > 0)
{
tun2SocksAddress = Utils.List2String(lstAddresses);

View file

@ -21,9 +21,10 @@ public class BaseFmt
protected static int ToUriQuery(ProfileItem item, string? securityDef, ref Dictionary<string, string> dicQuery)
{
if (item.Flow.IsNotEmpty())
var extraItem = item.GetExtraItem();
if (!extraItem.Flow.IsNullOrEmpty())
{
dicQuery.Add("flow", item.Flow);
dicQuery.Add("flow", extraItem.Flow);
}
if (item.StreamSecurity.IsNotEmpty())
@ -208,7 +209,9 @@ public class BaseFmt
protected static int ResolveUriQuery(NameValueCollection query, ref ProfileItem item)
{
item.Flow = GetQueryValue(query, "flow");
var extraItem = item.GetExtraItem();
extraItem.Flow = GetQueryValue(query, "flow");
item.SetExtraItem(extraItem);
item.StreamSecurity = GetQueryValue(query, "security");
item.Sni = GetQueryValue(query, "sni");
item.Alpn = GetQueryDecoded(query, "alpn");

View file

@ -24,11 +24,15 @@ public class Hysteria2Fmt : BaseFmt
var query = Utils.ParseQueryString(url.Query);
ResolveUriQuery(query, ref item);
item.Path = GetQueryDecoded(query, "obfs-password");
item.Ports = GetQueryDecoded(query, "mport");
if (item.CertSha.IsNullOrEmpty())
{
item.CertSha = GetQueryDecoded(query, "pinSHA256");
}
ProtocolExtraItem extraItem = new()
{
Ports = GetQueryDecoded(query, "mport")
};
item.SetExtraItem(extraItem);
return item;
}
@ -55,9 +59,10 @@ public class Hysteria2Fmt : BaseFmt
dicQuery.Add("obfs", "salamander");
dicQuery.Add("obfs-password", Utils.UrlEncode(item.Path));
}
if (item.Ports.IsNotEmpty())
var extra = item.GetExtraItem();
if (extra?.Ports?.IsNotEmpty() ?? false)
{
dicQuery.Add("mport", Utils.UrlEncode(item.Ports.Replace(':', '-')));
dicQuery.Add("mport", Utils.UrlEncode(extra.Ports.Replace(':', '-')));
}
if (!item.CertSha.IsNullOrEmpty())
{

View file

@ -23,6 +23,8 @@ public class VmessFmt : BaseFmt
{
return null;
}
var extraItem = item?.GetExtraItem();
var vmessQRCode = new VmessQRCode
{
v = item.ConfigVersion,
@ -30,7 +32,7 @@ public class VmessFmt : BaseFmt
add = item.Address,
port = item.Port,
id = item.Id,
aid = item.AlterId,
aid = int.TryParse(extraItem?.AlterId, out var result) ? result : 0,
scy = item.Security,
net = item.Network,
type = item.HeaderType,
@ -76,7 +78,10 @@ public class VmessFmt : BaseFmt
item.Address = Utils.ToString(vmessQRCode.add);
item.Port = vmessQRCode.port;
item.Id = Utils.ToString(vmessQRCode.id);
item.AlterId = vmessQRCode.aid;
item.SetExtraItem(new ProtocolExtraItem
{
AlterId = vmessQRCode.aid.ToString(),
});
item.Security = Utils.ToString(vmessQRCode.scy);
item.Security = vmessQRCode.scy.IsNotEmpty() ? vmessQRCode.scy : Global.DefaultSecurity;

View file

@ -128,6 +128,8 @@ public class ActionPrecheckManager
}
}
var extraItem = item.GetExtraItem();
switch (item.ConfigType)
{
case EConfigType.VMess:
@ -144,7 +146,7 @@ public class ActionPrecheckManager
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
}
if (!Global.Flows.Contains(item.Flow))
if (!Global.Flows.Contains(extraItem.Flow ?? string.Empty))
{
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
}
@ -202,37 +204,22 @@ public class ActionPrecheckManager
{
var errors = new List<string>();
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
}
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
var hasCycle = await GroupProfileManager.HasCycle(item.IndexId, item.GetExtraItem());
if (hasCycle)
{
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
return errors;
}
var childIds = new List<string>();
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
childIds.AddRangeSafe(subItems.Select(p => p.IndexId));
childIds.AddRangeSafe(Utils.String2List(group.ChildItems));
var (childItems, _) = await GroupProfileManager.GetChildProfileItems(item);
foreach (var child in childIds)
foreach (var childItem in childItems)
{
var childErrors = new List<string>();
if (child.IsNullOrEmpty())
{
continue;
}
var childItem = await AppManager.Instance.GetProfileItem(child);
if (childItem is null)
{
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
childErrors.Add(string.Format(ResUI.NodeTagNotExist, ""));
continue;
}

View file

@ -94,6 +94,8 @@ public sealed class AppManager
_ = StatePort;
_ = StatePort2;
_ = MigrateProfileExtra();
return true;
}
@ -225,15 +227,6 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks);
}
public async Task<ProfileGroupItem?> GetProfileGroupItem(string indexId)
{
if (indexId.IsNullOrEmpty())
{
return null;
}
return await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().FirstOrDefaultAsync(it => it.IndexId == indexId);
}
public async Task<List<RoutingItem>?> RoutingItems()
{
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync();
@ -264,6 +257,40 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
}
public async Task MigrateProfileExtra()
{
var list = await SQLiteHelper.Instance.TableAsync<ProfileItem>().ToListAsync();
foreach (var item in list)
{
if (!item.JsonData.IsNullOrEmpty())
{
return;
}
ProtocolExtraItem extra = new()
{
AlterId = item.AlterId.ToString(),
Flow = item.Flow.IsNotEmpty() ? item.Flow : null,
Ports = item.Ports.IsNotEmpty() ? item.Ports : null,
};
if (item.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
extra.GroupType = nameof(item.ConfigType);
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var groupItem);
if (groupItem != null && !groupItem.NotHasChild())
{
extra.ChildItems = groupItem.ChildItems;
extra.SubChildItems = groupItem.SubChildItems;
extra.Filter = groupItem.Filter;
extra.MultipleLoad = groupItem.MultipleLoad;
}
}
item.SetExtraItem(extra);
await SQLiteHelper.Instance.UpdateAsync(item);
}
await ProfileGroupItemManager.Instance.ClearAll();
}
#endregion SqliteHelper
#region Core Type

View file

@ -0,0 +1,175 @@
namespace ServiceLib.Manager;
public class GroupProfileManager
{
public static async Task<bool> HasCycle(ProfileItem item)
{
return await HasCycle(item.IndexId, item.GetExtraItem());
}
public static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo)
{
return await HasCycle(indexId, extraInfo, new HashSet<string>(), new HashSet<string>());
}
public static async Task<bool> HasCycle(string? indexId, ProtocolExtraItem? extraInfo, HashSet<string> visited, HashSet<string> stack)
{
if (indexId.IsNullOrEmpty() || extraInfo == null)
{
return false;
}
if (stack.Contains(indexId))
{
return true;
}
if (visited.Contains(indexId))
{
return false;
}
visited.Add(indexId);
stack.Add(indexId);
try
{
if (extraInfo.GroupType.IsNullOrEmpty())
{
return false;
}
if (extraInfo.ChildItems.IsNullOrEmpty())
{
return false;
}
var childIds = Utils.String2List(extraInfo.ChildItems)
?.Where(p => !string.IsNullOrEmpty(p))
.ToList();
if (childIds == null)
{
return false;
}
foreach (var child in childIds)
{
var childItem = await AppManager.Instance.GetProfileItem(child);
if (await HasCycle(child, childItem?.GetExtraItem(), visited, stack))
{
return true;
}
}
return false;
}
finally
{
stack.Remove(indexId);
}
}
public static async Task<(List<ProfileItem> Items, ProtocolExtraItem? Extra)> GetChildProfileItems(ProfileItem profileItem)
{
var profileExtra = profileItem?.GetExtraItem();
if (profileExtra == null)
{
return ([], null);
}
var items = await GetChildProfileItems(profileExtra);
var subItems = await GetSubChildProfileItems(profileExtra);
items.AddRange(subItems);
return (items, profileExtra);
}
public static async Task<List<ProfileItem>> GetChildProfileItems(ProtocolExtraItem? extra)
{
if (extra == null || extra.ChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = (await Task.WhenAll(
Utils.String2List(extra.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
))
.Where(p =>
p != null &&
p.IsValid() &&
p.ConfigType != EConfigType.Custom
)
.ToList();
return childProfiles;
}
public static async Task<List<ProfileItem>> GetSubChildProfileItems(ProtocolExtraItem? extra)
{
if (extra == null || extra.Filter.IsNullOrEmpty())
{
return new();
}
var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty);
return childProfiles.Where(p =>
p != null &&
p.IsValid() &&
!p.ConfigType.IsComplexType() &&
(extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter))
)
.ToList();
}
public static async Task<HashSet<string>> GetAllChildDomainAddresses(ProfileItem profileItem)
{
var childAddresses = new HashSet<string>();
var (childItems, _) = await GetChildProfileItems(profileItem);
foreach (var child in childItems)
{
if (!child.IsComplex())
{
childAddresses.Add(child.Address);
}
else if (child.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(child);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
public static async Task<HashSet<string>> GetAllChildEchQuerySni(ProfileItem profileItem)
{
var childAddresses = new HashSet<string>();
var (childItems, _) = await GetChildProfileItems(profileItem);
foreach (var childNode in childItems)
{
if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty())
{
if (childNode.StreamSecurity == Global.StreamSecurity
&& childNode.EchConfigList?.Contains("://") == true)
{
var idx = childNode.EchConfigList.IndexOf('+');
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
}
else
{
childAddresses.Add(childNode.Sni);
}
}
else if (childNode.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(childNode);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
}

View file

@ -29,11 +29,6 @@ public class ProfileGroupItemManager
return _items.TryGetValue(indexId, out item);
}
public ProfileGroupItem? GetOrDefault(string indexId)
{
return string.IsNullOrWhiteSpace(indexId) ? null : (_items.TryGetValue(indexId, out var v) ? v : null);
}
private async Task InitData()
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem where IndexId not in ( select indexId from ProfileItem )");
@ -42,347 +37,9 @@ public class ProfileGroupItemManager
_items = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!));
}
private ProfileGroupItem AddProfileGroupItem(string indexId)
{
var profileGroupItem = new ProfileGroupItem()
{
IndexId = indexId,
ChildItems = string.Empty,
MultipleLoad = EMultipleLoad.LeastPing
};
_items[indexId] = profileGroupItem;
return profileGroupItem;
}
private ProfileGroupItem GetProfileGroupItem(string indexId)
{
if (string.IsNullOrEmpty(indexId))
{
indexId = Utils.GetGuid(false);
}
return _items.GetOrAdd(indexId, AddProfileGroupItem);
}
public async Task ClearAll()
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem ");
_items.Clear();
}
public async Task SaveTo()
{
try
{
var lstExists = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
var existsMap = lstExists.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!);
var lstInserts = new List<ProfileGroupItem>();
var lstUpdates = new List<ProfileGroupItem>();
foreach (var item in _items.Values)
{
if (string.IsNullOrEmpty(item.IndexId))
{
continue;
}
if (existsMap.ContainsKey(item.IndexId))
{
lstUpdates.Add(item);
}
else
{
lstInserts.Add(item);
}
}
try
{
if (lstInserts.Count > 0)
{
await SQLiteHelper.Instance.InsertAllAsync(lstInserts);
}
if (lstUpdates.Count > 0)
{
await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
public ProfileGroupItem GetOrCreateAndMarkDirty(string indexId)
{
return GetProfileGroupItem(indexId);
}
public async ValueTask DisposeAsync()
{
await SaveTo();
}
public async Task SaveItemAsync(ProfileGroupItem item)
{
if (item is null)
{
throw new ArgumentNullException(nameof(item));
}
if (string.IsNullOrWhiteSpace(item.IndexId))
{
throw new ArgumentException("IndexId required", nameof(item));
}
_items[item.IndexId] = item;
try
{
var lst = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().Where(t => t.IndexId == item.IndexId).ToListAsync();
if (lst != null && lst.Count > 0)
{
await SQLiteHelper.Instance.UpdateAllAsync(new List<ProfileGroupItem> { item });
}
else
{
await SQLiteHelper.Instance.InsertAllAsync(new List<ProfileGroupItem> { item });
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
#region Helper
public static bool HasCycle(string? indexId)
{
return HasCycle(indexId, new HashSet<string>(), new HashSet<string>());
}
public static bool HasCycle(string? indexId, HashSet<string> visited, HashSet<string> stack)
{
if (indexId.IsNullOrEmpty())
{
return false;
}
if (stack.Contains(indexId))
{
return true;
}
if (visited.Contains(indexId))
{
return false;
}
visited.Add(indexId);
stack.Add(indexId);
try
{
Instance.TryGet(indexId, out var groupItem);
if (groupItem == null || groupItem.ChildItems.IsNullOrEmpty())
{
return false;
}
var childIds = Utils.String2List(groupItem.ChildItems)
.Where(p => !string.IsNullOrEmpty(p))
.ToList();
if (childIds == null)
{
return false;
}
foreach (var child in childIds)
{
if (HasCycle(child, visited, stack))
{
return true;
}
}
return false;
}
finally
{
stack.Remove(indexId);
}
}
public static async Task<(List<ProfileItem> Items, ProfileGroupItem? Group)> GetChildProfileItems(string? indexId)
{
Instance.TryGet(indexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.NotHasChild())
{
return (new List<ProfileItem>(), profileGroupItem);
}
var items = new List<ProfileItem>();
items.AddRange(await GetSubChildProfileItems(profileGroupItem));
items.AddRange(await GetChildProfileItems(profileGroupItem));
return (items, profileGroupItem);
}
public static async Task<List<ProfileItem>> GetChildProfileItems(ProfileGroupItem? group)
{
if (group == null || group.ChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = (await Task.WhenAll(
Utils.String2List(group.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
))
.Where(p =>
p != null &&
p.IsValid() &&
p.ConfigType != EConfigType.Custom
)
.ToList();
return childProfiles;
}
public static async Task<List<ProfileItem>> GetSubChildProfileItems(ProfileGroupItem? group)
{
if (group == null || group.SubChildItems.IsNullOrEmpty())
{
return new();
}
var childProfiles = await AppManager.Instance.ProfileItems(group.SubChildItems);
return childProfiles.Where(p =>
p != null &&
p.IsValid() &&
!p.ConfigType.IsComplexType() &&
(group.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, group.Filter))
)
.ToList();
}
public static async Task<HashSet<string>> GetAllChildDomainAddresses(string indexId)
{
// include grand children
var childAddresses = new HashSet<string>();
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null)
{
return childAddresses;
}
if (groupItem.SubChildItems.IsNotEmpty())
{
var subItems = await GetSubChildProfileItems(groupItem);
subItems.ForEach(p => childAddresses.Add(p.Address));
}
var childIds = Utils.String2List(groupItem.ChildItems) ?? [];
foreach (var childId in childIds)
{
var childNode = await AppManager.Instance.GetProfileItem(childId);
if (childNode == null)
{
continue;
}
if (!childNode.IsComplex())
{
childAddresses.Add(childNode.Address);
}
else if (childNode.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
public static async Task<HashSet<string>> GetAllChildEchQuerySni(string indexId)
{
// include grand children
var childAddresses = new HashSet<string>();
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null)
{
return childAddresses;
}
if (groupItem.SubChildItems.IsNotEmpty())
{
var subItems = await GetSubChildProfileItems(groupItem);
foreach (var childNode in subItems)
{
if (childNode.EchConfigList.IsNullOrEmpty())
{
continue;
}
if (childNode.StreamSecurity == Global.StreamSecurity
&& childNode.EchConfigList?.Contains("://") == true)
{
var idx = childNode.EchConfigList.IndexOf('+');
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
}
else
{
childAddresses.Add(childNode.Sni);
}
}
}
var childIds = Utils.String2List(groupItem.ChildItems) ?? [];
foreach (var childId in childIds)
{
var childNode = await AppManager.Instance.GetProfileItem(childId);
if (childNode == null)
{
continue;
}
if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty())
{
if (childNode.StreamSecurity == Global.StreamSecurity
&& childNode.EchConfigList?.Contains("://") == true)
{
var idx = childNode.EchConfigList.IndexOf('+');
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
}
else
{
childAddresses.Add(childNode.Sni);
}
}
else if (childNode.ConfigType.IsGroupType())
{
var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId);
foreach (var addr in subAddresses)
{
childAddresses.Add(addr);
}
}
}
return childAddresses;
}
#endregion Helper
}

View file

@ -1,5 +1,6 @@
namespace ServiceLib.Models;
// deprecated
[Serializable]
public class ProfileGroupItem
{

View file

@ -94,7 +94,7 @@ public class ProfileItem : ReactiveObject
return false;
}
if (!Global.Flows.Contains(Flow))
if (!Global.Flows.Contains(GetExtraItem().Flow ?? string.Empty))
{
return false;
}
@ -125,6 +125,20 @@ public class ProfileItem : ReactiveObject
return true;
}
public void SetExtraItem(ProtocolExtraItem extraItem)
{
JsonData = JsonUtils.Serialize(extraItem, false);
}
public ProtocolExtraItem GetExtraItem()
{
if (JsonData.IsNullOrEmpty())
{
return new ProtocolExtraItem();
}
return JsonUtils.Deserialize<ProtocolExtraItem>(JsonData);
}
#endregion function
[PrimaryKey]
@ -134,9 +148,7 @@ public class ProfileItem : ReactiveObject
public int ConfigVersion { get; set; }
public string Address { get; set; }
public int Port { get; set; }
public string Ports { get; set; }
public string Id { get; set; }
public int AlterId { get; set; }
public string Security { get; set; }
public string Network { get; set; }
public string Remarks { get; set; }
@ -147,7 +159,6 @@ public class ProfileItem : ReactiveObject
public string AllowInsecure { get; set; }
public string Subid { get; set; }
public bool IsSub { get; set; } = true;
public string Flow { get; set; }
public string Sni { get; set; }
public string Alpn { get; set; } = string.Empty;
public ECoreType? CoreType { get; set; }
@ -164,4 +175,9 @@ public class ProfileItem : ReactiveObject
public string CertSha { get; set; }
public string EchConfigList { get; set; }
public string EchForceQuery { get; set; }
public string JsonData { get; set; }
// deprecated
public string Ports { get; set; }
public int AlterId { get; set; }
public string Flow { get; set; }
}

View file

@ -0,0 +1,27 @@
namespace ServiceLib.Models;
public class ProtocolExtraItem
{
// vmess
public string? AlterId { get; set; }
// vless
public string? Flow { get; set; }
//public string? VisionSeed { get; set; }
// shadowsocks
//public string? PluginArgs { get; set; }
// hysteria2
public string? UpMbps { get; set; }
public string? DownMbps { get; set; }
public string? Ports { get; set; }
public string? HopInterval { get; set; }
// group profile
public string? GroupType { get; set; }
public string? ChildItems { get; set; }
public string? SubChildItems { get; set; }
public string? Filter { get; set; }
public EMultipleLoad? MultipleLoad { get; set; }
}

View file

@ -204,7 +204,7 @@ public partial class CoreConfigSingboxService
}
else if (node?.ConfigType.IsGroupType() == true)
{
var queryServerNames = (await ProfileGroupItemManager.GetAllChildEchQuerySni(node.IndexId)).ToList();
var queryServerNames = (await GroupProfileManager.GetAllChildEchQuerySni(node)).ToList();
if (queryServerNames.Count > 0)
{
singboxConfig.dns.rules.Add(new()

View file

@ -6,6 +6,7 @@ public partial class CoreConfigSingboxService
{
try
{
var extraItem = node.GetExtraItem();
outbound.server = node.Address;
outbound.server_port = node.Port;
outbound.type = Global.ProtocolTypes[node.ConfigType];
@ -15,7 +16,7 @@ public partial class CoreConfigSingboxService
case EConfigType.VMess:
{
outbound.uuid = node.Id;
outbound.alter_id = node.AlterId;
outbound.alter_id = int.TryParse(extraItem?.AlterId, out var result) ? result : 0;
if (Global.VmessSecurities.Contains(node.Security))
{
outbound.security = node.Security;
@ -112,13 +113,13 @@ public partial class CoreConfigSingboxService
outbound.packet_encoding = "xudp";
if (node.Flow.IsNullOrEmpty())
if (extraItem.Flow.IsNullOrEmpty())
{
await GenOutboundMux(node, outbound);
}
else
{
outbound.flow = node.Flow;
outbound.flow = extraItem.Flow;
}
await GenOutboundTransport(node, outbound);
@ -145,12 +146,14 @@ public partial class CoreConfigSingboxService
};
}
outbound.up_mbps = _config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null;
outbound.down_mbps = _config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null;
if (node.Ports.IsNotEmpty() && (node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(',')))
var extra = node.GetExtraItem();
outbound.up_mbps = int.TryParse(extra?.UpMbps, out var upMbps) && upMbps >= 0 ? upMbps : (_config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null);
outbound.down_mbps = int.TryParse(extra?.DownMbps, out var downMbps) && downMbps >= 0 ? downMbps : (_config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null);
var ports = extra?.Ports?.IsNullOrEmpty() == false ? extra.Ports : null;
if ((!ports.IsNullOrEmpty()) && (ports.Contains(':') || ports.Contains('-') || ports.Contains(',')))
{
outbound.server_port = null;
outbound.server_ports = node.Ports.Split(',')
outbound.server_ports = ports.Split(',')
.Select(p => p.Trim())
.Where(p => p.IsNotEmpty())
.Select(p =>
@ -453,13 +456,13 @@ public partial class CoreConfigSingboxService
{
return -1;
}
var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId);
var hasCycle = await GroupProfileManager.HasCycle(node);
if (hasCycle)
{
return -1;
}
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
return -1;
@ -467,13 +470,14 @@ public partial class CoreConfigSingboxService
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing;
if (ignoreOriginChain)
{
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
await GenOutboundsList(childProfiles, singboxConfig, multipleLoad, baseTagName);
}
else
{
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
await GenOutboundsListWithChain(childProfiles, singboxConfig, multipleLoad, baseTagName);
}
break;
@ -585,7 +589,7 @@ public partial class CoreConfigSingboxService
if (node.ConfigType.IsGroupType())
{
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
continue;
@ -594,7 +598,8 @@ public partial class CoreConfigSingboxService
var ret = node.ConfigType switch
{
EConfigType.PolicyGroup =>
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName),
await GenOutboundsListWithChain(childProfiles, singboxConfig,
profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing, childBaseTagName),
EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName),
_ => throw new NotImplementedException()
@ -763,7 +768,7 @@ public partial class CoreConfigSingboxService
if (node.ConfigType.IsGroupType())
{
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
continue;
@ -772,7 +777,7 @@ public partial class CoreConfigSingboxService
var ret = node.ConfigType switch
{
EConfigType.PolicyGroup =>
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName),
await GenOutboundsList(childProfiles, singboxConfig, profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing, childBaseTagName),
EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName),
_ => throw new NotImplementedException()

View file

@ -6,6 +6,7 @@ public partial class CoreConfigV2rayService
{
try
{
var extraItem = node.GetExtraItem();
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
switch (node.ConfigType)
{
@ -36,7 +37,7 @@ public partial class CoreConfigV2rayService
}
usersItem.id = node.Id;
usersItem.alterId = node.AlterId;
usersItem.alterId = int.TryParse(extraItem?.AlterId, out var result) ? result : 0;
usersItem.email = Global.UserEMail;
if (Global.VmessSecurities.Contains(node.Security))
{
@ -142,13 +143,13 @@ public partial class CoreConfigV2rayService
usersItem.email = Global.UserEMail;
usersItem.encryption = node.Security;
if (node.Flow.IsNullOrEmpty())
if (extraItem.Flow.IsNullOrEmpty())
{
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled);
}
else
{
usersItem.flow = node.Flow;
usersItem.flow = extraItem.Flow;
await GenOutboundMux(node, outbound, false, muxEnabled);
}
outbound.settings.servers = null;
@ -509,13 +510,15 @@ public partial class CoreConfigV2rayService
break;
case "hysteria":
var extraItem = node.GetExtraItem();
var ports = extraItem?.Ports;
HysteriaUdpHop4Ray? udpHop = null;
if (node.Ports.IsNotEmpty() &&
(node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(',')))
if (!ports.IsNullOrEmpty() &&
(ports.Contains(':') || ports.Contains('-') || ports.Contains(',')))
{
udpHop = new()
{
ports = node.Ports.Replace(':', '-'),
ports = ports.Replace(':', '-'),
interval = _config.HysteriaItem.HopInterval > 0
? _config.HysteriaItem.HopInterval
: null,
@ -592,13 +595,13 @@ public partial class CoreConfigV2rayService
{
return -1;
}
var hasCycle = ProfileGroupItemManager.HasCycle(node.IndexId);
var hasCycle = await GroupProfileManager.HasCycle(node);
if (hasCycle)
{
return -1;
}
var (childProfiles, profileGroupItem) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
return -1;
@ -627,8 +630,9 @@ public partial class CoreConfigV2rayService
//add balancers
if (node.ConfigType == EConfigType.PolicyGroup)
{
await GenObservatory(v2rayConfig, profileGroupItem.MultipleLoad, baseTagName);
await GenBalancer(v2rayConfig, profileGroupItem.MultipleLoad, baseTagName);
var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing;
await GenObservatory(v2rayConfig, multipleLoad, baseTagName);
await GenBalancer(v2rayConfig, multipleLoad, baseTagName);
}
}
catch (Exception ex)
@ -737,7 +741,7 @@ public partial class CoreConfigV2rayService
if (node.ConfigType.IsGroupType())
{
var (childProfiles, _) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
continue;
@ -891,7 +895,7 @@ public partial class CoreConfigV2rayService
if (node.ConfigType.IsGroupType())
{
var (childProfiles, _) = await ProfileGroupItemManager.GetChildProfileItems(node.IndexId);
var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node);
if (childProfiles.Count <= 0)
{
continue;

View file

@ -79,8 +79,8 @@ public class AddGroupServerViewModel : MyReactiveObject
public async Task Init()
{
ProfileGroupItemManager.Instance.TryGet(SelectedSource.IndexId, out var profileGroup);
PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
var extra = SelectedSource.GetExtraItem();
PolicyGroupType = (extra?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
{
EMultipleLoad.LeastPing => ResUI.TbLeastPing,
EMultipleLoad.Fallback => ResUI.TbFallback,
@ -93,13 +93,10 @@ public class AddGroupServerViewModel : MyReactiveObject
var subs = await AppManager.Instance.SubItems();
subs.Add(new SubItem());
SubItems.AddRange(subs);
SelectedSubItem = SubItems.Where(s => s.Id == profileGroup?.SubChildItems).FirstOrDefault();
Filter = profileGroup?.Filter;
SelectedSubItem = SubItems.FirstOrDefault(s => s.Id == extra?.SubChildItems);
Filter = extra?.Filter;
var childItemMulti = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource?.IndexId);
if (childItemMulti != null)
{
var childIndexIds = Utils.String2List(childItemMulti.ChildItems) ?? [];
var childIndexIds = Utils.String2List(extra?.ChildItems) ?? [];
foreach (var item in childIndexIds)
{
var child = await AppManager.Instance.GetProfileItem(item);
@ -110,7 +107,6 @@ public class AddGroupServerViewModel : MyReactiveObject
ChildItemsObs.Add(child);
}
}
}
public async Task ChildRemoveAsync()
{
@ -205,18 +201,11 @@ public class AddGroupServerViewModel : MyReactiveObject
{
return;
}
var childIndexIds = new List<string>();
foreach (var item in ChildItemsObs)
{
if (item.IndexId.IsNullOrEmpty())
{
continue;
}
childIndexIds.Add(item.IndexId);
}
var profileGroup = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource.IndexId);
profileGroup.ChildItems = Utils.List2String(childIndexIds);
profileGroup.MultipleLoad = PolicyGroupType switch
var extra = SelectedSource.GetExtraItem();
extra.ChildItems =
Utils.List2String(ChildItemsObs.Where(s => !s.IndexId.IsNullOrEmpty()).Select(s => s.IndexId).ToList());
extra.MultipleLoad = PolicyGroupType switch
{
var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing,
var s when s == ResUI.TbFallback => EMultipleLoad.Fallback,
@ -226,17 +215,19 @@ public class AddGroupServerViewModel : MyReactiveObject
_ => EMultipleLoad.LeastPing,
};
profileGroup.SubChildItems = SelectedSubItem?.Id;
profileGroup.Filter = Filter;
extra.SubChildItems = SelectedSubItem?.Id;
extra.Filter = Filter;
var hasCycle = ProfileGroupItemManager.HasCycle(profileGroup.IndexId);
var hasCycle = await GroupProfileManager.HasCycle(SelectedSource.IndexId, extra);
if (hasCycle)
{
NoticeManager.Instance.Enqueue(string.Format(ResUI.GroupSelfReference, remarks));
return;
}
if (await ConfigHandler.AddGroupServerCommon(_config, SelectedSource, profileGroup, true) == 0)
SelectedSource.SetExtraItem(extra);
if (await ConfigHandler.AddServerCommon(_config, SelectedSource) == 0)
{
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
_updateView?.Invoke(EViewAction.CloseWindow, null);

View file

@ -17,6 +17,15 @@ public class AddServerViewModel : MyReactiveObject
[Reactive]
public string CertSha { get; set; }
[Reactive]
public int AlterId { get; set; }
[Reactive]
public string Ports { get; set; }
[Reactive]
public string Flow { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
@ -63,6 +72,11 @@ public class AddServerViewModel : MyReactiveObject
CoreType = SelectedSource?.CoreType?.ToString();
Cert = SelectedSource?.Cert?.ToString() ?? string.Empty;
CertSha = SelectedSource?.CertSha?.ToString() ?? string.Empty;
var extraItem = SelectedSource?.GetExtraItem();
Ports = extraItem?.Ports ?? string.Empty;
AlterId = int.TryParse(extraItem?.AlterId, out var result) ? result : 0;
Flow = extraItem?.Flow ?? string.Empty;
}
private async Task SaveServerAsync()
@ -109,6 +123,11 @@ public class AddServerViewModel : MyReactiveObject
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
SelectedSource.Cert = Cert.IsNullOrEmpty() ? string.Empty : Cert;
SelectedSource.CertSha = CertSha.IsNullOrEmpty() ? string.Empty : CertSha;
var extraItem = SelectedSource.GetExtraItem();
extraItem.Ports = Ports;
extraItem.AlterId = AlterId > 0 ? AlterId.ToString() : string.Empty;
extraItem.Flow = Flow;
SelectedSource.SetExtraItem(extraItem);
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
{

View file

@ -120,7 +120,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
{
case EConfigType.VMess:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
break;
@ -139,21 +139,21 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
case EConfigType.VLESS:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables);
break;
case EConfigType.Trojan:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
break;
case EConfigType.Hysteria2:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
break;
case EConfigType.TUIC:

View file

@ -115,7 +115,7 @@ public partial class AddServerWindow
{
case EConfigType.VMess:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.cmbSecurity.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
break;
@ -134,21 +134,21 @@ public partial class AddServerWindow
case EConfigType.VLESS:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Security, v => v.txtSecurity5.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables);
break;
case EConfigType.Trojan:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Flow, v => v.cmbFlow6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
break;
case EConfigType.Hysteria2:
this.Bind(ViewModel, vm => vm.SelectedSource.Id, v => v.txtId7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Path, v => v.txtPath7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
break;
case EConfigType.TUIC: