mirror of
https://github.com/2dust/v2rayN.git
synced 2025-10-26 18:24:43 +00:00
Compare commits
18 commits
2a7095aa1e
...
8c1d48f2d0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c1d48f2d0 | ||
|
|
54fcbafe2a | ||
|
|
05e598e5fa | ||
|
|
f11707011d | ||
|
|
2482e599cc | ||
|
|
1ff017ee4d | ||
|
|
db3c1a1f15 | ||
|
|
ed6ddefcd9 | ||
|
|
2f3fcb2584 | ||
|
|
25845f79a2 | ||
|
|
b31f02511a | ||
|
|
8e44e202ca | ||
|
|
b8d40c1d05 | ||
|
|
68027332fe | ||
|
|
3a89be9ddd | ||
|
|
c20d5b3208 | ||
|
|
54e83391d0 | ||
|
|
3e0578f775 |
52 changed files with 2695 additions and 309 deletions
|
|
@ -331,6 +331,32 @@ public class Utils
|
|||
.ToList();
|
||||
}
|
||||
|
||||
public static Dictionary<string, List<string>> ParseHostsToDictionary(string hostsContent)
|
||||
{
|
||||
var userHostsMap = hostsContent
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
// skip full-line comments
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
|
||||
// strip inline comments (truncate at '#')
|
||||
.Select(line =>
|
||||
{
|
||||
var index = line.IndexOf('#');
|
||||
return index >= 0 ? line.Substring(0, index).Trim() : line;
|
||||
})
|
||||
// ensure line still contains valid parts
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
.Where(parts => parts.Length >= 2)
|
||||
.GroupBy(parts => parts[0])
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||
);
|
||||
|
||||
return userHostsMap;
|
||||
}
|
||||
|
||||
#endregion 转换函数
|
||||
|
||||
#region 数据检查
|
||||
|
|
@ -857,6 +883,55 @@ public class Utils
|
|||
return false;
|
||||
}
|
||||
|
||||
public static bool IsPackagedInstall()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsWindows() || IsOSX())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPIMAGE")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var exePath = GetExePath();
|
||||
var baseDir = string.IsNullOrEmpty(exePath) ? StartupPath() : Path.GetDirectoryName(exePath) ?? "";
|
||||
var p = baseDir.Replace('\\', '/');
|
||||
|
||||
if (string.IsNullOrEmpty(p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p.Contains("/.mount_", StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p.StartsWith("/opt/v2rayN", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p.StartsWith("/usr/lib/v2rayN", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p.StartsWith("/usr/share/v2rayN", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static async Task<string?> GetLinuxUserId()
|
||||
{
|
||||
var arg = new List<string>() { "-c", "id -u" };
|
||||
|
|
|
|||
|
|
@ -12,5 +12,9 @@ public enum EConfigType
|
|||
TUIC = 8,
|
||||
WireGuard = 9,
|
||||
HTTP = 10,
|
||||
Anytls = 11
|
||||
Anytls = 11,
|
||||
|
||||
Group = 1000,
|
||||
PolicyGroup = 1001,
|
||||
ProxyChain = 1002,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ namespace ServiceLib.Enums;
|
|||
|
||||
public enum EMultipleLoad
|
||||
{
|
||||
LeastPing,
|
||||
Fallback,
|
||||
Random,
|
||||
RoundRobin,
|
||||
LeastPing,
|
||||
LeastLoad
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public enum EViewAction
|
|||
RoutingRuleDetailsWindow,
|
||||
AddServerWindow,
|
||||
AddServer2Window,
|
||||
AddGroupServerWindow,
|
||||
DNSSettingWindow,
|
||||
RoutingSettingWindow,
|
||||
OptionSettingWindow,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public class Global
|
|||
public const string ProxySetLinuxShellFileName = NamespaceSample + "proxy_set_linux_sh";
|
||||
public const string KillAsSudoOSXShellFileName = NamespaceSample + "kill_as_sudo_osx_sh";
|
||||
public const string KillAsSudoLinuxShellFileName = NamespaceSample + "kill_as_sudo_linux_sh";
|
||||
public const string SingboxFakeIPFilterFileName = NamespaceSample + "singbox_fakeip_filter";
|
||||
|
||||
public const string DefaultSecurity = "auto";
|
||||
public const string DefaultNetwork = "tcp";
|
||||
|
|
@ -49,6 +50,7 @@ public class Global
|
|||
public const string DirectTag = "direct";
|
||||
public const string BlockTag = "block";
|
||||
public const string DnsTag = "dns-module";
|
||||
public const string BalancerTagSuffix = "-round";
|
||||
public const string StreamSecurity = "tls";
|
||||
public const string StreamSecurityReality = "reality";
|
||||
public const string Loopback = "127.0.0.1";
|
||||
|
|
|
|||
|
|
@ -113,6 +113,10 @@ public static class ConfigHandler
|
|||
config.ConstItem ??= new ConstItem();
|
||||
|
||||
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
||||
if (config.SimpleDNSItem.GlobalFakeIp is null)
|
||||
{
|
||||
config.SimpleDNSItem.GlobalFakeIp = true;
|
||||
}
|
||||
|
||||
config.SpeedTestItem ??= new();
|
||||
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
||||
|
|
@ -353,6 +357,11 @@ public static class ConfigHandler
|
|||
{
|
||||
}
|
||||
}
|
||||
else if (profileItem.ConfigType > EConfigType.Group)
|
||||
{
|
||||
var profileGroupItem = await AppManager.Instance.GetProfileGroupItem(it.IndexId);
|
||||
await AddGroupServerCommon(config, profileItem, profileGroupItem, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await AddServerCommon(config, profileItem, true);
|
||||
|
|
@ -1070,6 +1079,35 @@ 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();
|
||||
}
|
||||
if (maxSort > 0)
|
||||
{
|
||||
ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
|
||||
}
|
||||
if (toFile)
|
||||
{
|
||||
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
|
||||
if (profileGroupItem != null)
|
||||
{
|
||||
profileGroupItem.ParentIndexId = 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
|
||||
|
|
@ -1141,7 +1179,7 @@ public static class ConfigHandler
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a custom 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
|
||||
/// </summary>
|
||||
/// <param name="config">Current configuration</param>
|
||||
|
|
@ -1149,45 +1187,55 @@ public static class ConfigHandler
|
|||
/// <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>
|
||||
public static async Task<RetResult> AddCustomServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad)
|
||||
public static async Task<RetResult> AddGroupServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad, string? subId)
|
||||
{
|
||||
var indexId = Utils.GetMd5(Global.CoreMultipleLoadConfigFileName);
|
||||
var configPath = Utils.GetConfigPath(Global.CoreMultipleLoadConfigFileName);
|
||||
var result = new RetResult();
|
||||
|
||||
var result = await CoreConfigHandler.GenerateClientMultipleLoadConfig(config, configPath, selecteds, coreType, multipleLoad);
|
||||
if (result.Success != true)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
var indexId = Utils.GetGuid(false);
|
||||
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var profileItem = await AppManager.Instance.GetProfileItem(indexId) ?? new();
|
||||
profileItem.IndexId = indexId;
|
||||
var remark = string.Empty;
|
||||
if (coreType == ECoreType.Xray)
|
||||
{
|
||||
profileItem.Remarks = multipleLoad switch
|
||||
remark = multipleLoad switch
|
||||
{
|
||||
EMultipleLoad.Random => ResUI.menuSetDefaultMultipleServerXrayRandom,
|
||||
EMultipleLoad.RoundRobin => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
|
||||
EMultipleLoad.LeastPing => ResUI.menuSetDefaultMultipleServerXrayLeastPing,
|
||||
EMultipleLoad.LeastLoad => ResUI.menuSetDefaultMultipleServerXrayLeastLoad,
|
||||
_ => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
|
||||
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)
|
||||
{
|
||||
profileItem.Remarks = ResUI.menuSetDefaultMultipleServerSingBoxLeastPing;
|
||||
remark = multipleLoad switch
|
||||
{
|
||||
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
|
||||
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback,
|
||||
_ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
|
||||
};
|
||||
}
|
||||
profileItem.Address = Global.CoreMultipleLoadConfigFileName;
|
||||
profileItem.ConfigType = EConfigType.Custom;
|
||||
profileItem.CoreType = coreType;
|
||||
|
||||
await AddServerCommon(config, profileItem, true);
|
||||
|
||||
var profile = new ProfileItem
|
||||
{
|
||||
IndexId = indexId,
|
||||
CoreType = coreType,
|
||||
ConfigType = EConfigType.PolicyGroup,
|
||||
Remarks = remark,
|
||||
Address = childProfileIndexId,
|
||||
};
|
||||
if (!subId.IsNullOrEmpty())
|
||||
{
|
||||
profile.Subid = subId;
|
||||
}
|
||||
var profileGroup = new ProfileGroupItem
|
||||
{
|
||||
ChildItems = childProfileIndexId,
|
||||
MultipleLoad = multipleLoad,
|
||||
ParentIndexId = indexId,
|
||||
};
|
||||
var ret = await AddGroupServerCommon(config, profile, profileGroup, true);
|
||||
result.Success = ret == 0;
|
||||
result.Data = indexId;
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1203,7 +1251,7 @@ public static class ConfigHandler
|
|||
public static async Task<ProfileItem?> GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
|
||||
{
|
||||
ProfileItem? itemSocks = null;
|
||||
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
|
||||
if (node.ConfigType != EConfigType.Custom && node.ConfigType < EConfigType.Group && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
|
||||
{
|
||||
itemSocks = new ProfileItem()
|
||||
{
|
||||
|
|
@ -1214,7 +1262,7 @@ public static class ConfigHandler
|
|||
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
|
||||
};
|
||||
}
|
||||
else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0))
|
||||
else if ((node.ConfigType == EConfigType.Custom && node.ConfigType < EConfigType.Group && node.PreSocksPort > 0))
|
||||
{
|
||||
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
|
||||
itemSocks = new ProfileItem()
|
||||
|
|
@ -2221,6 +2269,7 @@ public static class ConfigHandler
|
|||
UseSystemHosts = false,
|
||||
AddCommonHosts = true,
|
||||
FakeIP = false,
|
||||
GlobalFakeIp = true,
|
||||
BlockBindingQuery = true,
|
||||
DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(),
|
||||
RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(),
|
||||
|
|
|
|||
|
|
@ -132,24 +132,4 @@ public static class CoreConfigHandler
|
|||
await File.WriteAllTextAsync(fileName, result.Data.ToString());
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<RetResult> GenerateClientMultipleLoadConfig(Config config, string fileName, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad)
|
||||
{
|
||||
var result = new RetResult();
|
||||
if (coreType == ECoreType.sing_box)
|
||||
{
|
||||
result = await new CoreConfigSingboxService(config).GenerateClientMultipleLoadConfig(selecteds);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = await new CoreConfigV2rayService(config).GenerateClientMultipleLoadConfig(selecteds, multipleLoad);
|
||||
}
|
||||
|
||||
if (result.Success != true)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
await File.WriteAllTextAsync(fileName, result.Data.ToString());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ public sealed class AppManager
|
|||
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
|
||||
SQLiteHelper.Instance.CreateTable<DNSItem>();
|
||||
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ProfileGroupItem>();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +102,7 @@ public sealed class AppManager
|
|||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
await ProfileExManager.Instance.SaveTo();
|
||||
await ProfileGroupItemManager.Instance.SaveTo();
|
||||
await StatisticsManager.Instance.SaveTo();
|
||||
await CoreManager.Instance.CoreStop();
|
||||
StatisticsManager.Instance.Close();
|
||||
|
|
@ -219,6 +221,15 @@ public sealed class AppManager
|
|||
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks);
|
||||
}
|
||||
|
||||
public async Task<ProfileGroupItem?> GetProfileGroupItem(string parentIndexId)
|
||||
{
|
||||
if (parentIndexId.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().FirstOrDefaultAsync(it => it.ParentIndexId == parentIndexId);
|
||||
}
|
||||
|
||||
public async Task<List<RoutingItem>?> RoutingItems()
|
||||
{
|
||||
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync();
|
||||
|
|
|
|||
167
v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs
Normal file
167
v2rayN/ServiceLib/Manager/ProfileGroupItemManager.cs
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
using System.Collections.Concurrent;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
|
||||
public class ProfileGroupItemManager
|
||||
{
|
||||
private static readonly Lazy<ProfileGroupItemManager> _instance = new(() => new());
|
||||
private ConcurrentDictionary<string, ProfileGroupItem> _items = new();
|
||||
|
||||
public static ProfileGroupItemManager Instance => _instance.Value;
|
||||
private static readonly string _tag = "ProfileGroupItemManager";
|
||||
|
||||
private ProfileGroupItemManager()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
await InitData();
|
||||
}
|
||||
|
||||
// Read-only getters: do not create or mark dirty
|
||||
public bool TryGet(string indexId, out ProfileGroupItem? item)
|
||||
{
|
||||
item = null;
|
||||
if (string.IsNullOrWhiteSpace(indexId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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 parentIndexId not in ( select indexId from ProfileItem )");
|
||||
|
||||
var list = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
|
||||
_items = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.ParentIndexId)).ToDictionary(t => t.ParentIndexId!));
|
||||
}
|
||||
|
||||
private ProfileGroupItem AddProfileGroupItem(string indexId)
|
||||
{
|
||||
var profileGroupItem = new ProfileGroupItem()
|
||||
{
|
||||
ParentIndexId = 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.ParentIndexId)).ToDictionary(t => t.ParentIndexId!);
|
||||
|
||||
var lstInserts = new List<ProfileGroupItem>();
|
||||
var lstUpdates = new List<ProfileGroupItem>();
|
||||
|
||||
foreach (var item in _items.Values)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.ParentIndexId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existsMap.ContainsKey(item.ParentIndexId))
|
||||
{
|
||||
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.ParentIndexId))
|
||||
{
|
||||
throw new ArgumentException("ParentIndexId required", nameof(item));
|
||||
}
|
||||
|
||||
_items[item.ParentIndexId] = item;
|
||||
|
||||
try
|
||||
{
|
||||
var lst = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().Where(t => t.ParentIndexId == item.ParentIndexId).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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ public class TaskManager
|
|||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
await ProfileExManager.Instance.SaveTo();
|
||||
await ProfileGroupItemManager.Instance.SaveTo();
|
||||
}
|
||||
|
||||
//Execute once 1 hour
|
||||
|
|
|
|||
|
|
@ -260,6 +260,7 @@ public class SimpleDNSItem
|
|||
public bool? UseSystemHosts { get; set; }
|
||||
public bool? AddCommonHosts { get; set; }
|
||||
public bool? FakeIP { get; set; }
|
||||
public bool? GlobalFakeIp { get; set; }
|
||||
public bool? BlockBindingQuery { get; set; }
|
||||
public string? DirectDNS { get; set; }
|
||||
public string? RemoteDNS { get; set; }
|
||||
|
|
|
|||
13
v2rayN/ServiceLib/Models/ProfileGroupItem.cs
Normal file
13
v2rayN/ServiceLib/Models/ProfileGroupItem.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using SQLite;
|
||||
namespace ServiceLib.Models;
|
||||
|
||||
[Serializable]
|
||||
public class ProfileGroupItem
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string ParentIndexId { get; set; }
|
||||
|
||||
public string ChildItems { get; set; }
|
||||
|
||||
public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing;
|
||||
}
|
||||
|
|
@ -39,11 +39,14 @@ public class ProfileItem : ReactiveObject
|
|||
> 1 => $"***{arrAddr.Last()}",
|
||||
_ => Address
|
||||
};
|
||||
summary += ConfigType switch
|
||||
if (ConfigType is EConfigType.Custom or > EConfigType.Group)
|
||||
{
|
||||
EConfigType.Custom => $"[{CoreType.ToString()}]{Remarks}",
|
||||
_ => $"{Remarks}({addr}:{Port})"
|
||||
};
|
||||
summary += $"[{CoreType.ToString()}]{Remarks}";
|
||||
}
|
||||
else
|
||||
{
|
||||
summary += $"{Remarks}({addr}:{Port})";
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ public class Outbound4Sbox : BaseServer4Sbox
|
|||
public string? plugin_opts { get; set; }
|
||||
public List<string>? outbounds { get; set; }
|
||||
public bool? interrupt_exist_connections { get; set; }
|
||||
public int? tolerance { get; set; }
|
||||
}
|
||||
|
||||
public class Endpoints4Sbox : BaseServer4Sbox
|
||||
|
|
|
|||
270
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
270
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
|
|
@ -672,6 +672,15 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Add Child Configuration 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuAddChildServer {
|
||||
get {
|
||||
return ResourceManager.GetString("menuAddChildServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Add a custom configuration Configuration 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -699,6 +708,24 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Add Policy Group Configuration 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuAddPolicyGroupServer {
|
||||
get {
|
||||
return ResourceManager.GetString("menuAddPolicyGroupServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Add Proxy Chain Configuration 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuAddProxyChainServer {
|
||||
get {
|
||||
return ResourceManager.GetString("menuAddProxyChainServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Import Share Links from clipboard (Ctrl+V) 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -951,6 +978,78 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Generate Policy Group from Multiple Profiles 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServer {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration Fallback by sing-box 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerSingBoxFallback {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxFallback", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerSingBoxLeastPing {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxLeastPing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration Fallback by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerXrayFallback {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerXrayFallback", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerXrayLeastLoad {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastLoad", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerXrayLeastPing {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerXrayLeastPing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerXrayRandom {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRandom", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuGenGroupMultipleServerXrayRoundRobin {
|
||||
get {
|
||||
return ResourceManager.GetString("menuGenGroupMultipleServerXrayRoundRobin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Global Hotkey Setting 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -1320,6 +1419,15 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Remove Child Configuration 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuRemoveChildServer {
|
||||
get {
|
||||
return ResourceManager.GetString("menuRemoveChildServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Remove duplicate Configurations 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -1473,6 +1581,15 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Server List 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuServerList {
|
||||
get {
|
||||
return ResourceManager.GetString("menuServerList", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Configurations 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -1482,60 +1599,6 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration to custom configuration 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuSetDefaultMultipleServer {
|
||||
get {
|
||||
return ResourceManager.GetString("menuSetDefaultMultipleServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuSetDefaultMultipleServerSingBoxLeastPing {
|
||||
get {
|
||||
return ResourceManager.GetString("menuSetDefaultMultipleServerSingBoxLeastPing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuSetDefaultMultipleServerXrayLeastLoad {
|
||||
get {
|
||||
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayLeastLoad", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration LeastPing by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuSetDefaultMultipleServerXrayLeastPing {
|
||||
get {
|
||||
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayLeastPing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration Random by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuSetDefaultMultipleServerXrayRandom {
|
||||
get {
|
||||
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayRandom", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Multi-Configuration RoundRobin by Xray 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuSetDefaultMultipleServerXrayRoundRobin {
|
||||
get {
|
||||
return ResourceManager.GetString("menuSetDefaultMultipleServerXrayRoundRobin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Set as active Configuration (Enter) 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -1995,6 +2058,15 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please Add At Least One Configuration 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string PleaseAddAtLeastOneServer {
|
||||
get {
|
||||
return ResourceManager.GetString("PleaseAddAtLeastOneServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please fill Remarks 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -2301,15 +2373,6 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Apply to Proxy Domains Only 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbApplyProxyDomainsOnly {
|
||||
get {
|
||||
return ResourceManager.GetString("TbApplyProxyDomainsOnly", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Auto refresh 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -2382,6 +2445,24 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Policy Group 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbConfigTypePolicyGroup {
|
||||
get {
|
||||
return ResourceManager.GetString("TbConfigTypePolicyGroup", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Proxy Chain 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbConfigTypeProxyChain {
|
||||
get {
|
||||
return ResourceManager.GetString("TbConfigTypeProxyChain", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Confirm 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -2526,6 +2607,24 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Applies globally by default, with built-in FakeIP filtering (sing-box only). 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbFakeIPTips {
|
||||
get {
|
||||
return ResourceManager.GetString("TbFakeIPTips", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fallback 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbFallback {
|
||||
get {
|
||||
return ResourceManager.GetString("TbFallback", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Fingerprint 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -2643,6 +2742,24 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Most Stable 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbLeastLoad {
|
||||
get {
|
||||
return ResourceManager.GetString("TbLeastLoad", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Lowest Latency 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbLeastPing {
|
||||
get {
|
||||
return ResourceManager.GetString("TbLeastPing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Address (IPv4, IPv6) 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -2697,6 +2814,15 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Policy Group Type 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbPolicyGroupType {
|
||||
get {
|
||||
return ResourceManager.GetString("TbPolicyGroupType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Port 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -2769,6 +2895,15 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Random 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbRandom {
|
||||
get {
|
||||
return ResourceManager.GetString("TbRandom", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 v2ray Full Config Template 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
@ -2832,6 +2967,15 @@ namespace ServiceLib.Resx {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Round Robin 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbRoundRobin {
|
||||
get {
|
||||
return ResourceManager.GetString("TbRoundRobin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1377,22 +1377,22 @@
|
|||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>مخفی و پورت می شود، با کاما (،) جدا می شود</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
|
||||
<value>چند سرور به پیکربندی سفارشی</value>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>Generate Policy Group from Multiple Profiles</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>چند سرور تصادفی توسط Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>چند سرور RoundRobin توسط Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>چند سرور LeastPing توسط Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>چند سرور LeastLoad توسط Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>LeastPing چند سرور توسط sing-box</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
|
|
@ -1455,9 +1455,6 @@
|
|||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||
</data>
|
||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
||||
<value>Apply to Proxy Domains Only</value>
|
||||
</data>
|
||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||
<value>Basic DNS Settings</value>
|
||||
</data>
|
||||
|
|
@ -1515,4 +1512,55 @@
|
|||
<data name="TbSelectProfile" xml:space="preserve">
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
</data>
|
||||
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
||||
<value>Policy Group</value>
|
||||
</data>
|
||||
<data name="TbConfigTypeProxyChain" xml:space="preserve">
|
||||
<value>Proxy Chain</value>
|
||||
</data>
|
||||
<data name="TbLeastPing" xml:space="preserve">
|
||||
<value>Lowest Latency</value>
|
||||
</data>
|
||||
<data name="TbRandom" xml:space="preserve">
|
||||
<value>Random</value>
|
||||
</data>
|
||||
<data name="TbRoundRobin" xml:space="preserve">
|
||||
<value>Round Robin</value>
|
||||
</data>
|
||||
<data name="TbLeastLoad" xml:space="preserve">
|
||||
<value>Most Stable</value>
|
||||
</data>
|
||||
<data name="TbPolicyGroupType" xml:space="preserve">
|
||||
<value>Policy Group Type</value>
|
||||
</data>
|
||||
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
||||
<value>Add Policy Group Configuration</value>
|
||||
</data>
|
||||
<data name="menuAddProxyChainServer" xml:space="preserve">
|
||||
<value>Add Proxy Chain Configuration</value>
|
||||
</data>
|
||||
<data name="menuAddChildServer" xml:space="preserve">
|
||||
<value>Add Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuRemoveChildServer" xml:space="preserve">
|
||||
<value>Remove Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>Server List</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>Fallback</value>
|
||||
</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>
|
||||
</root>
|
||||
|
|
@ -1377,22 +1377,22 @@
|
|||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>A portot lefedi, vesszővel (,) elválasztva</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
|
||||
<value>Több konfiguráció egyéni konfigurációra</value>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>Generate Policy Group from Multiple Profiles</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>Több konfiguráció véletlenszerűen Xray szerint</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>Több konfiguráció RoundRobin Xray szerint</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>Több konfiguráció legkisebb pinggel Xray szerint</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>Több konfiguráció legkisebb terheléssel Xray szerint</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>Több konfiguráció legkisebb pinggel sing-box szerint</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
|
|
@ -1455,9 +1455,6 @@
|
|||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||
</data>
|
||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
||||
<value>Apply to Proxy Domains Only</value>
|
||||
</data>
|
||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||
<value>Basic DNS Settings</value>
|
||||
</data>
|
||||
|
|
@ -1515,4 +1512,55 @@
|
|||
<data name="TbSelectProfile" xml:space="preserve">
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
</data>
|
||||
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
||||
<value>Policy Group</value>
|
||||
</data>
|
||||
<data name="TbConfigTypeProxyChain" xml:space="preserve">
|
||||
<value>Proxy Chain</value>
|
||||
</data>
|
||||
<data name="TbLeastPing" xml:space="preserve">
|
||||
<value>Lowest Latency</value>
|
||||
</data>
|
||||
<data name="TbRandom" xml:space="preserve">
|
||||
<value>Random</value>
|
||||
</data>
|
||||
<data name="TbRoundRobin" xml:space="preserve">
|
||||
<value>Round Robin</value>
|
||||
</data>
|
||||
<data name="TbLeastLoad" xml:space="preserve">
|
||||
<value>Most Stable</value>
|
||||
</data>
|
||||
<data name="TbPolicyGroupType" xml:space="preserve">
|
||||
<value>Policy Group Type</value>
|
||||
</data>
|
||||
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
||||
<value>Add Policy Group Configuration</value>
|
||||
</data>
|
||||
<data name="menuAddProxyChainServer" xml:space="preserve">
|
||||
<value>Add Proxy Chain Configuration</value>
|
||||
</data>
|
||||
<data name="menuAddChildServer" xml:space="preserve">
|
||||
<value>Add Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuRemoveChildServer" xml:space="preserve">
|
||||
<value>Remove Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>Server List</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>Fallback</value>
|
||||
</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>
|
||||
</root>
|
||||
|
|
@ -1377,22 +1377,22 @@
|
|||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>Will cover the port, separate with commas (,)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
|
||||
<value>Multi-Configuration to custom configuration</value>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>Generate Policy Group from Multiple Profiles</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>Multi-Configuration Random by Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>Multi-Configuration RoundRobin by Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>Multi-Configuration LeastPing by Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>Multi-Configuration LeastLoad by Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>Multi-Configuration LeastPing by sing-box</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
|
|
@ -1455,9 +1455,6 @@
|
|||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||
</data>
|
||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
||||
<value>Apply to Proxy Domains Only</value>
|
||||
</data>
|
||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||
<value>Basic DNS Settings</value>
|
||||
</data>
|
||||
|
|
@ -1515,4 +1512,55 @@
|
|||
<data name="TbSelectProfile" xml:space="preserve">
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
</data>
|
||||
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
||||
<value>Policy Group</value>
|
||||
</data>
|
||||
<data name="TbConfigTypeProxyChain" xml:space="preserve">
|
||||
<value>Proxy Chain</value>
|
||||
</data>
|
||||
<data name="TbLeastPing" xml:space="preserve">
|
||||
<value>Lowest Latency</value>
|
||||
</data>
|
||||
<data name="TbRandom" xml:space="preserve">
|
||||
<value>Random</value>
|
||||
</data>
|
||||
<data name="TbRoundRobin" xml:space="preserve">
|
||||
<value>Round Robin</value>
|
||||
</data>
|
||||
<data name="TbLeastLoad" xml:space="preserve">
|
||||
<value>Most Stable</value>
|
||||
</data>
|
||||
<data name="TbPolicyGroupType" xml:space="preserve">
|
||||
<value>Policy Group Type</value>
|
||||
</data>
|
||||
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
||||
<value>Add Policy Group Configuration</value>
|
||||
</data>
|
||||
<data name="menuAddProxyChainServer" xml:space="preserve">
|
||||
<value>Add Proxy Chain Configuration</value>
|
||||
</data>
|
||||
<data name="menuAddChildServer" xml:space="preserve">
|
||||
<value>Add Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuRemoveChildServer" xml:space="preserve">
|
||||
<value>Remove Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>Server List</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>Fallback</value>
|
||||
</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>
|
||||
</root>
|
||||
|
|
@ -1377,22 +1377,22 @@
|
|||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>Заменит указанный порт, перечисляйте через запятую (,)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
|
||||
<value>От мультиконфигурации к пользовательской конфигурации</value>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>Generate Policy Group from Multiple Profiles</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>Случайный (Xray)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>Круговой (Xray)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>Минимальное RTT (минимальное время туда-обратно) (Xray)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>Минимальная нагрузка (Xray)</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>Минимальное RTT (минимальное время туда-обратно) (sing-box)</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
|
|
@ -1455,9 +1455,6 @@
|
|||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||
<value>DNS hosts: (каждая строка в формате "domain1 ip1 ip2")</value>
|
||||
</data>
|
||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
||||
<value>Применять только к доменам через прокси</value>
|
||||
</data>
|
||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||
<value>Базовые настройки DNS</value>
|
||||
</data>
|
||||
|
|
@ -1515,4 +1512,55 @@
|
|||
<data name="TbSelectProfile" xml:space="preserve">
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
</data>
|
||||
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
||||
<value>Policy Group</value>
|
||||
</data>
|
||||
<data name="TbConfigTypeProxyChain" xml:space="preserve">
|
||||
<value>Proxy Chain</value>
|
||||
</data>
|
||||
<data name="TbLeastPing" xml:space="preserve">
|
||||
<value>Lowest Latency</value>
|
||||
</data>
|
||||
<data name="TbRandom" xml:space="preserve">
|
||||
<value>Random</value>
|
||||
</data>
|
||||
<data name="TbRoundRobin" xml:space="preserve">
|
||||
<value>Round Robin</value>
|
||||
</data>
|
||||
<data name="TbLeastLoad" xml:space="preserve">
|
||||
<value>Most Stable</value>
|
||||
</data>
|
||||
<data name="TbPolicyGroupType" xml:space="preserve">
|
||||
<value>Policy Group Type</value>
|
||||
</data>
|
||||
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
||||
<value>Add Policy Group Configuration</value>
|
||||
</data>
|
||||
<data name="menuAddProxyChainServer" xml:space="preserve">
|
||||
<value>Add Proxy Chain Configuration</value>
|
||||
</data>
|
||||
<data name="menuAddChildServer" xml:space="preserve">
|
||||
<value>Add Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuRemoveChildServer" xml:space="preserve">
|
||||
<value>Remove Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>Server List</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>Fallback</value>
|
||||
</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>
|
||||
</root>
|
||||
|
|
@ -1374,22 +1374,22 @@
|
|||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>会覆盖端口,多组时用逗号 (,) 隔开</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
|
||||
<value>多配置文件产生自定义配置 (多选)</value>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>多配置文件生成策略组</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>多配置文件随机 Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>多配置文件负载均衡 Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>多配置文件最低延迟 Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>多配置文件最稳定 Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>多配置文件最低延迟 sing-box</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
|
|
@ -1452,9 +1452,6 @@
|
|||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||
<value>DNS Hosts:(“域名1 ip1 ip2” 一行一个)</value>
|
||||
</data>
|
||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
||||
<value>仅对代理域名生效</value>
|
||||
</data>
|
||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||
<value>DNS 基础设置</value>
|
||||
</data>
|
||||
|
|
@ -1512,4 +1509,55 @@
|
|||
<data name="TbSelectProfile" xml:space="preserve">
|
||||
<value>选择配置文件</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>请至少添加一个配置文件</value>
|
||||
</data>
|
||||
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
||||
<value>策略组</value>
|
||||
</data>
|
||||
<data name="TbConfigTypeProxyChain" xml:space="preserve">
|
||||
<value>链式代理</value>
|
||||
</data>
|
||||
<data name="TbLeastPing" xml:space="preserve">
|
||||
<value>最低延迟</value>
|
||||
</data>
|
||||
<data name="TbRandom" xml:space="preserve">
|
||||
<value>随机</value>
|
||||
</data>
|
||||
<data name="TbRoundRobin" xml:space="preserve">
|
||||
<value>负载均衡</value>
|
||||
</data>
|
||||
<data name="TbLeastLoad" xml:space="preserve">
|
||||
<value>最稳定</value>
|
||||
</data>
|
||||
<data name="TbPolicyGroupType" xml:space="preserve">
|
||||
<value>策略组类型</value>
|
||||
</data>
|
||||
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
||||
<value>添加策略组配置文件</value>
|
||||
</data>
|
||||
<data name="menuAddProxyChainServer" xml:space="preserve">
|
||||
<value>添加链式代理配置文件</value>
|
||||
</data>
|
||||
<data name="menuAddChildServer" xml:space="preserve">
|
||||
<value>添加子配置文件</value>
|
||||
</data>
|
||||
<data name="menuRemoveChildServer" xml:space="preserve">
|
||||
<value>删除子配置文件</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>服务器列表</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>故障转移</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
|
||||
<value>多配置文件故障转移 sing-box</value>
|
||||
</data>
|
||||
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
|
||||
<value>多配置文件故障转移 Xray</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -1374,22 +1374,22 @@
|
|||
<data name="TbPorts7Tips" xml:space="preserve">
|
||||
<value>會覆蓋埠,多組時用逗號 (,) 隔開</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
|
||||
<value>多設定檔產生自訂配置 (多選)</value>
|
||||
<data name="menuGenGroupMultipleServer" xml:space="preserve">
|
||||
<value>Generate Policy Group from Multiple Profiles</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
|
||||
<value>多設定檔隨機 Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
|
||||
<value>多設定檔負載平衡 Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
|
||||
<value>多設定檔最低延遲 Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
|
||||
<value>多設定檔最穩定 Xray</value>
|
||||
</data>
|
||||
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
|
||||
<value>多設定檔最低延遲 sing-box</value>
|
||||
</data>
|
||||
<data name="menuExportConfig" xml:space="preserve">
|
||||
|
|
@ -1452,9 +1452,6 @@
|
|||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||
</data>
|
||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
||||
<value>Apply to Proxy Domains Only</value>
|
||||
</data>
|
||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||
<value>Basic DNS Settings</value>
|
||||
</data>
|
||||
|
|
@ -1512,4 +1509,55 @@
|
|||
<data name="TbSelectProfile" xml:space="preserve">
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
</data>
|
||||
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
|
||||
<value>Policy Group</value>
|
||||
</data>
|
||||
<data name="TbConfigTypeProxyChain" xml:space="preserve">
|
||||
<value>Proxy Chain</value>
|
||||
</data>
|
||||
<data name="TbLeastPing" xml:space="preserve">
|
||||
<value>Lowest Latency</value>
|
||||
</data>
|
||||
<data name="TbRandom" xml:space="preserve">
|
||||
<value>Random</value>
|
||||
</data>
|
||||
<data name="TbRoundRobin" xml:space="preserve">
|
||||
<value>Round Robin</value>
|
||||
</data>
|
||||
<data name="TbLeastLoad" xml:space="preserve">
|
||||
<value>Most Stable</value>
|
||||
</data>
|
||||
<data name="TbPolicyGroupType" xml:space="preserve">
|
||||
<value>Policy Group Type</value>
|
||||
</data>
|
||||
<data name="menuAddPolicyGroupServer" xml:space="preserve">
|
||||
<value>Add Policy Group Configuration</value>
|
||||
</data>
|
||||
<data name="menuAddProxyChainServer" xml:space="preserve">
|
||||
<value>Add Proxy Chain Configuration</value>
|
||||
</data>
|
||||
<data name="menuAddChildServer" xml:space="preserve">
|
||||
<value>Add Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuRemoveChildServer" xml:space="preserve">
|
||||
<value>Remove Child Configuration</value>
|
||||
</data>
|
||||
<data name="menuServerList" xml:space="preserve">
|
||||
<value>Server List</value>
|
||||
</data>
|
||||
<data name="TbFallback" xml:space="preserve">
|
||||
<value>Fallback</value>
|
||||
</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>
|
||||
</root>
|
||||
92
v2rayN/ServiceLib/Sample/singbox_fakeip_filter
Normal file
92
v2rayN/ServiceLib/Sample/singbox_fakeip_filter
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"domain": [
|
||||
"amobile.music.tc.qq.com",
|
||||
"api-jooxtt.sanook.com",
|
||||
"api.joox.com",
|
||||
"aqqmusic.tc.qq.com",
|
||||
"dl.stream.qqmusic.qq.com",
|
||||
"ff.dorado.sdo.com",
|
||||
"heartbeat.belkin.com",
|
||||
"isure.stream.qqmusic.qq.com",
|
||||
"joox.com",
|
||||
"lens.l.google.com",
|
||||
"localhost.ptlogin2.qq.com",
|
||||
"localhost.sec.qq.com",
|
||||
"mesu.apple.com",
|
||||
"mobileoc.music.tc.qq.com",
|
||||
"music.taihe.com",
|
||||
"musicapi.taihe.com",
|
||||
"na.b.g-tun.com",
|
||||
"proxy.golang.org",
|
||||
"ps.res.netease.com",
|
||||
"shark007.net",
|
||||
"songsearch.kugou.com",
|
||||
"static.adtidy.org",
|
||||
"streamoc.music.tc.qq.com",
|
||||
"swcdn.apple.com",
|
||||
"swdist.apple.com",
|
||||
"swdownload.apple.com",
|
||||
"swquery.apple.com",
|
||||
"swscan.apple.com",
|
||||
"trackercdn.kugou.com",
|
||||
"xnotify.xboxlive.com"
|
||||
],
|
||||
"domain_keyword": [
|
||||
"ntp",
|
||||
"stun",
|
||||
"time"
|
||||
],
|
||||
"domain_regex": [
|
||||
"^[^.]+$",
|
||||
"^[^.]+\\.[^.]+\\.xboxlive\\.com$",
|
||||
"^localhost\\.[^.]+\\.weixin\\.qq\\.com$",
|
||||
"^mijia\\scloud$",
|
||||
"^xbox\\.[^.]+\\.microsoft\\.com$",
|
||||
"^xbox\\.[^.]+\\.[^.]+\\.microsoft\\.com$"
|
||||
],
|
||||
"domain_suffix": [
|
||||
"126.net",
|
||||
"3gppnetwork.org",
|
||||
"battle.net",
|
||||
"battlenet.com.cn",
|
||||
"cdn.nintendo.net",
|
||||
"cmbchina.com",
|
||||
"cmbimg.com",
|
||||
"ff14.sdo.com",
|
||||
"ffxiv.com",
|
||||
"finalfantasyxiv.com",
|
||||
"gcloudcs.com",
|
||||
"home.arpa",
|
||||
"invalid",
|
||||
"kuwo.cn",
|
||||
"lan",
|
||||
"linksys.com",
|
||||
"linksyssmartwifi.com",
|
||||
"local",
|
||||
"localdomain",
|
||||
"localhost",
|
||||
"market.xiaomi.com",
|
||||
"mcdn.bilivideo.cn",
|
||||
"media.dssott.com",
|
||||
"msftconnecttest.com",
|
||||
"msftncsi.com",
|
||||
"music.163.com",
|
||||
"music.migu.cn",
|
||||
"n0808.com",
|
||||
"nflxvideo.net",
|
||||
"oray.com",
|
||||
"orayimg.com",
|
||||
"router.asus.com",
|
||||
"sandai.net",
|
||||
"square-enix.com",
|
||||
"srv.nintendo.net",
|
||||
"steamcontent.com",
|
||||
"uu.163.com",
|
||||
"wargaming.net",
|
||||
"wggames.cn",
|
||||
"wotgame.cn",
|
||||
"wowsgame.cn",
|
||||
"xiami.com",
|
||||
"y.qq.com"
|
||||
]
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@
|
|||
<EmbeddedResource Include="Sample\tun_singbox_inbound" />
|
||||
<EmbeddedResource Include="Sample\tun_singbox_rules" />
|
||||
<EmbeddedResource Include="Sample\linux_autostart_config" />
|
||||
<EmbeddedResource Include="Sample\singbox_fakeip_filter" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,33 @@ public partial class CoreConfigSingboxService(Config config)
|
|||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (node?.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
||||
{
|
||||
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
|
||||
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
var childProfiles = (await Task.WhenAll(
|
||||
Utils.String2List(profileGroupItem.ChildItems)
|
||||
.Where(p => !p.IsNullOrEmpty())
|
||||
.Select(AppManager.Instance.GetProfileItem)
|
||||
)).Where(p => p != null).ToList();
|
||||
if (childProfiles.Count <= 0)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
switch (node.ConfigType)
|
||||
{
|
||||
case EConfigType.PolicyGroup:
|
||||
return await GenerateClientMultipleLoadConfig(childProfiles, profileGroupItem.MultipleLoad);
|
||||
case EConfigType.ProxyChain:
|
||||
return await GenerateClientChainConfig(childProfiles);
|
||||
}
|
||||
}
|
||||
|
||||
if (node == null
|
||||
|| node.Port <= 0)
|
||||
{
|
||||
|
|
@ -344,7 +371,105 @@ public partial class CoreConfigSingboxService(Config config)
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds)
|
||||
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (singboxConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
singboxConfig.outbounds.RemoveAt(0);
|
||||
|
||||
await GenLog(singboxConfig);
|
||||
await GenInbounds(singboxConfig);
|
||||
await GenRouting(singboxConfig);
|
||||
await GenExperimental(singboxConfig);
|
||||
|
||||
var proxyProfiles = new List<ProfileItem>();
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (it.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
||||
{
|
||||
var itemGroup = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
proxyProfiles.Add(itemGroup);
|
||||
}
|
||||
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.Port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
|
||||
{
|
||||
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (item.ConfigType == EConfigType.Shadowsocks
|
||||
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//outbound
|
||||
proxyProfiles.Add(item);
|
||||
}
|
||||
if (proxyProfiles.Count <= 0)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
await GenOutboundsList(proxyProfiles, singboxConfig, multipleLoad);
|
||||
|
||||
await GenDns(null, singboxConfig);
|
||||
await ConvertGeo2Ruleset(singboxConfig);
|
||||
|
||||
ret.Success = true;
|
||||
|
||||
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RetResult> GenerateClientChainConfig(List<ProfileItem> selecteds)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
|
|
@ -419,7 +544,7 @@ public partial class CoreConfigSingboxService(Config config)
|
|||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
await GenOutboundsList(proxyProfiles, singboxConfig);
|
||||
await GenChainOutboundsList(proxyProfiles, singboxConfig);
|
||||
|
||||
await GenDns(null, singboxConfig);
|
||||
await ConvertGeo2Ruleset(singboxConfig);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,15 @@ public partial class CoreConfigSingboxService
|
|||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
||||
}
|
||||
singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
|
||||
if ((!useDirectDns) && simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
|
||||
{
|
||||
singboxConfig.dns.rules.Add(new()
|
||||
{
|
||||
server = Global.SingboxFakeDNSTag,
|
||||
query_type = new List<int> { 1, 28 }, // A and AAAA
|
||||
rewrite_ttl = 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Tun2SocksAddress
|
||||
if (node != null && Utils.IsDomain(node.Address))
|
||||
|
|
@ -94,17 +103,7 @@ public partial class CoreConfigSingboxService
|
|||
|
||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||
{
|
||||
var userHostsMap = simpleDNSItem.Hosts
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
.Where(parts => parts.Length >= 2)
|
||||
.GroupBy(parts => parts[0])
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||
);
|
||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||
|
||||
foreach (var kvp in userHostsMap)
|
||||
{
|
||||
|
|
@ -197,6 +196,28 @@ public partial class CoreConfigSingboxService
|
|||
});
|
||||
}
|
||||
|
||||
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == true)
|
||||
{
|
||||
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
|
||||
fakeipFilterRule.invert = true;
|
||||
var rule4Fake = new Rule4Sbox
|
||||
{
|
||||
server = Global.SingboxFakeDNSTag,
|
||||
type = "logical",
|
||||
mode = "and",
|
||||
rewrite_ttl = 1,
|
||||
rules = new List<Rule4Sbox>
|
||||
{
|
||||
new() {
|
||||
query_type = new List<int> { 1, 28 }, // A and AAAA
|
||||
},
|
||||
fakeipFilterRule,
|
||||
}
|
||||
};
|
||||
|
||||
singboxConfig.dns.rules.Add(rule4Fake);
|
||||
}
|
||||
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
if (routing == null)
|
||||
return 0;
|
||||
|
|
@ -276,10 +297,12 @@ public partial class CoreConfigSingboxService
|
|||
}
|
||||
else
|
||||
{
|
||||
if (simpleDNSItem.FakeIP == true)
|
||||
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
|
||||
{
|
||||
var rule4Fake = JsonUtils.DeepCopy(rule);
|
||||
rule4Fake.server = Global.SingboxFakeDNSTag;
|
||||
rule4Fake.query_type = new List<int> { 1, 28 }; // A and AAAA
|
||||
rule4Fake.rewrite_ttl = 1;
|
||||
singboxConfig.dns.rules.Add(rule4Fake);
|
||||
}
|
||||
rule.server = Global.SingboxRemoteDNSTag;
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ public partial class CoreConfigSingboxService
|
|||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig)
|
||||
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -438,6 +438,38 @@ public partial class CoreConfigSingboxService
|
|||
{
|
||||
index++;
|
||||
|
||||
if (node.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
||||
{
|
||||
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
|
||||
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var childProfiles = (await Task.WhenAll(
|
||||
Utils.String2List(profileGroupItem.ChildItems)
|
||||
.Where(p => !p.IsNullOrEmpty())
|
||||
.Select(AppManager.Instance.GetProfileItem)
|
||||
)).Where(p => p != null).ToList();
|
||||
if (childProfiles.Count <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var childBaseTagName = $"{baseTagName}-{index}";
|
||||
var ret = node.ConfigType switch
|
||||
{
|
||||
EConfigType.PolicyGroup =>
|
||||
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName),
|
||||
EConfigType.ProxyChain =>
|
||||
await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
if (ret == 0)
|
||||
{
|
||||
proxyTags.Add(childBaseTagName);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle proxy chain
|
||||
string? prevTag = null;
|
||||
var currentServer = await GenServer(node);
|
||||
|
|
@ -450,7 +482,7 @@ public partial class CoreConfigSingboxService
|
|||
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||
|
||||
// current proxy
|
||||
currentServer.tag = $"{Global.ProxyTag}-{index}";
|
||||
currentServer.tag = $"{baseTagName}-{index}";
|
||||
proxyTags.Add(currentServer.tag);
|
||||
|
||||
if (!node.Subid.IsNullOrEmpty())
|
||||
|
|
@ -467,7 +499,7 @@ public partial class CoreConfigSingboxService
|
|||
{
|
||||
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||
await GenOutbound(prevNode, prevOutbound);
|
||||
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
|
||||
prevTag = $"prev-{baseTagName}-{++prevIndex}";
|
||||
prevOutbound.tag = prevTag;
|
||||
prevOutbounds.Add(prevOutbound);
|
||||
}
|
||||
|
|
@ -508,16 +540,21 @@ public partial class CoreConfigSingboxService
|
|||
var outUrltest = new Outbound4Sbox
|
||||
{
|
||||
type = "urltest",
|
||||
tag = $"{Global.ProxyTag}-auto",
|
||||
tag = $"{baseTagName}-auto",
|
||||
outbounds = proxyTags,
|
||||
interrupt_exist_connections = false,
|
||||
};
|
||||
|
||||
if (multipleLoad == EMultipleLoad.Fallback)
|
||||
{
|
||||
outUrltest.tolerance = 5000;
|
||||
}
|
||||
|
||||
// Add selector outbound (manual selection)
|
||||
var outSelector = new Outbound4Sbox
|
||||
{
|
||||
type = "selector",
|
||||
tag = Global.ProxyTag,
|
||||
tag = baseTagName,
|
||||
outbounds = JsonUtils.DeepCopy(proxyTags),
|
||||
interrupt_exist_connections = false,
|
||||
};
|
||||
|
|
@ -574,4 +611,52 @@ public partial class CoreConfigSingboxService
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<int> GenChainOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag)
|
||||
{
|
||||
var resultOutbounds = new List<Outbound4Sbox>();
|
||||
var resultEndpoints = new List<Endpoints4Sbox>(); // For endpoints
|
||||
for (var i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
var node = nodes[i];
|
||||
var server = await GenServer(node);
|
||||
|
||||
if (server is null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
server.tag = baseTagName;
|
||||
}
|
||||
else
|
||||
{
|
||||
server.tag = baseTagName + i.ToString();
|
||||
}
|
||||
|
||||
if (i != nodes.Count - 1)
|
||||
{
|
||||
server.detour = baseTagName + (i + 1).ToString();
|
||||
}
|
||||
|
||||
if (server is Endpoints4Sbox endpoint)
|
||||
{
|
||||
resultEndpoints.Add(endpoint);
|
||||
}
|
||||
else if (server is Outbound4Sbox outbound)
|
||||
{
|
||||
resultOutbounds.Add(outbound);
|
||||
}
|
||||
}
|
||||
singboxConfig.outbounds ??= new();
|
||||
resultOutbounds.AddRange(singboxConfig.outbounds);
|
||||
singboxConfig.outbounds = resultOutbounds;
|
||||
|
||||
singboxConfig.endpoints ??= new();
|
||||
resultEndpoints.AddRange(singboxConfig.endpoints);
|
||||
singboxConfig.endpoints = resultEndpoints;
|
||||
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,31 @@ public partial class CoreConfigSingboxService
|
|||
});
|
||||
}
|
||||
|
||||
var hostsDomains = new List<string>();
|
||||
var systemHostsMap = Utils.GetSystemHosts();
|
||||
foreach (var kvp in systemHostsMap)
|
||||
{
|
||||
hostsDomains.Add(kvp.Key);
|
||||
}
|
||||
var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
if (dnsItem == null || dnsItem.Enabled == false)
|
||||
{
|
||||
var simpleDNSItem = _config.SimpleDNSItem;
|
||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||
{
|
||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||
foreach (var kvp in userHostsMap)
|
||||
{
|
||||
hostsDomains.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
action = "resolve",
|
||||
domain = hostsDomains,
|
||||
});
|
||||
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
|
|
@ -337,19 +362,60 @@ public partial class CoreConfigSingboxService
|
|||
}
|
||||
|
||||
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||
|
||||
if (node == null
|
||||
|| !Global.SingboxSupportConfigType.Contains(node.ConfigType))
|
||||
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
|
||||
&& node.ConfigType is not (EConfigType.PolicyGroup or EConfigType.ProxyChain)))
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
var tag = Global.ProxyTag + node.IndexId.ToString();
|
||||
if (singboxConfig.outbounds.Any(o => o.tag == tag)
|
||||
|| (singboxConfig.endpoints != null && singboxConfig.endpoints.Any(e => e.tag == tag)))
|
||||
{
|
||||
return tag;
|
||||
}
|
||||
|
||||
if (node.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
||||
{
|
||||
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
|
||||
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
var childProfiles = (await Task.WhenAll(
|
||||
Utils.String2List(profileGroupItem.ChildItems)
|
||||
.Where(p => !p.IsNullOrEmpty())
|
||||
.Select(AppManager.Instance.GetProfileItem)
|
||||
)).Where(p => p != null).ToList();
|
||||
if (childProfiles.Count <= 0)
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
var childBaseTagName = $"{Global.ProxyTag}-{node.IndexId}";
|
||||
var ret = node.ConfigType switch
|
||||
{
|
||||
EConfigType.PolicyGroup =>
|
||||
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName),
|
||||
EConfigType.ProxyChain =>
|
||||
await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
if (ret == 0)
|
||||
{
|
||||
return childBaseTagName;
|
||||
}
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
var server = await GenServer(node);
|
||||
if (server is null)
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
server.tag = Global.ProxyTag + node.IndexId.ToString();
|
||||
server.tag = tag;
|
||||
if (server is Endpoints4Sbox endpoint)
|
||||
{
|
||||
singboxConfig.endpoints ??= new();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,33 @@ public partial class CoreConfigV2rayService(Config config)
|
|||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (node?.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
||||
{
|
||||
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
|
||||
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
var childProfiles = (await Task.WhenAll(
|
||||
Utils.String2List(profileGroupItem.ChildItems)
|
||||
.Where(p => !p.IsNullOrEmpty())
|
||||
.Select(AppManager.Instance.GetProfileItem)
|
||||
)).Where(p => p != null).ToList();
|
||||
if (childProfiles.Count <= 0)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
switch (node.ConfigType)
|
||||
{
|
||||
case EConfigType.PolicyGroup:
|
||||
return await GenerateClientMultipleLoadConfig(childProfiles, profileGroupItem.MultipleLoad);
|
||||
case EConfigType.ProxyChain:
|
||||
return await GenerateClientChainConfig(childProfiles);
|
||||
}
|
||||
}
|
||||
|
||||
if (node == null
|
||||
|| node.Port <= 0)
|
||||
{
|
||||
|
|
@ -75,6 +102,156 @@ public partial class CoreConfigV2rayService(Config config)
|
|||
{
|
||||
var ret = new RetResult();
|
||||
|
||||
try
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||
string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||
if (v2rayConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
v2rayConfig.outbounds.RemoveAt(0);
|
||||
|
||||
await GenLog(v2rayConfig);
|
||||
await GenInbounds(v2rayConfig);
|
||||
await GenRouting(v2rayConfig);
|
||||
await GenDns(null, v2rayConfig);
|
||||
await GenStatistic(v2rayConfig);
|
||||
|
||||
var proxyProfiles = new List<ProfileItem>();
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (it.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
||||
{
|
||||
var itemGroup = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
proxyProfiles.Add(itemGroup);
|
||||
}
|
||||
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.Port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
|
||||
{
|
||||
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (item.ConfigType == EConfigType.Shadowsocks
|
||||
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//outbound
|
||||
proxyProfiles.Add(item);
|
||||
}
|
||||
if (proxyProfiles.Count <= 0)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
await GenOutboundsList(proxyProfiles, v2rayConfig);
|
||||
|
||||
//add balancers
|
||||
await GenObservatory(v2rayConfig, multipleLoad);
|
||||
await GenBalancer(v2rayConfig, multipleLoad);
|
||||
|
||||
var defaultBalancerTag = $"{Global.ProxyTag}{Global.BalancerTagSuffix}";
|
||||
|
||||
//add rule
|
||||
var rules = v2rayConfig.routing.rules;
|
||||
if (rules?.Count > 0)
|
||||
{
|
||||
var balancerTagSet = v2rayConfig.routing.balancers
|
||||
.Select(b => b.tag)
|
||||
.ToHashSet();
|
||||
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
if (rule.outboundTag == null)
|
||||
continue;
|
||||
|
||||
if (balancerTagSet.Contains(rule.outboundTag))
|
||||
{
|
||||
rule.balancerTag = rule.outboundTag;
|
||||
rule.outboundTag = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
var outboundWithSuffix = rule.outboundTag + Global.BalancerTagSuffix;
|
||||
if (balancerTagSet.Contains(outboundWithSuffix))
|
||||
{
|
||||
rule.balancerTag = outboundWithSuffix;
|
||||
rule.outboundTag = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
||||
{
|
||||
v2rayConfig.routing.rules.Add(new()
|
||||
{
|
||||
ip = ["0.0.0.0/0", "::/0"],
|
||||
balancerTag = defaultBalancerTag,
|
||||
type = "field"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
v2rayConfig.routing.rules.Add(new()
|
||||
{
|
||||
network = "tcp,udp",
|
||||
balancerTag = defaultBalancerTag,
|
||||
type = "field"
|
||||
});
|
||||
}
|
||||
|
||||
ret.Success = true;
|
||||
|
||||
ret.Data = await ApplyFullConfigTemplate(v2rayConfig, true);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RetResult> GenerateClientChainConfig(List<ProfileItem> selecteds)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
|
||||
try
|
||||
{
|
||||
if (_config == null)
|
||||
|
|
@ -148,41 +325,7 @@ public partial class CoreConfigV2rayService(Config config)
|
|||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
await GenOutboundsList(proxyProfiles, v2rayConfig);
|
||||
|
||||
//add balancers
|
||||
await GenBalancer(v2rayConfig, multipleLoad);
|
||||
|
||||
var balancer = v2rayConfig.routing.balancers.First();
|
||||
|
||||
//add rule
|
||||
var rules = v2rayConfig.routing.rules.Where(t => t.outboundTag == Global.ProxyTag).ToList();
|
||||
if (rules?.Count > 0)
|
||||
{
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
rule.outboundTag = null;
|
||||
rule.balancerTag = balancer.tag;
|
||||
}
|
||||
}
|
||||
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
||||
{
|
||||
v2rayConfig.routing.rules.Add(new()
|
||||
{
|
||||
ip = ["0.0.0.0/0", "::/0"],
|
||||
balancerTag = balancer.tag,
|
||||
type = "field"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
v2rayConfig.routing.rules.Add(new()
|
||||
{
|
||||
network = "tcp,udp",
|
||||
balancerTag = balancer.tag,
|
||||
type = "field"
|
||||
});
|
||||
}
|
||||
await GenChainOutboundsList(proxyProfiles, v2rayConfig);
|
||||
|
||||
ret.Success = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
|
|||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
|
||||
private async Task<int> GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
|
||||
{
|
||||
if (multipleLoad == EMultipleLoad.LeastPing)
|
||||
{
|
||||
|
|
@ -15,7 +15,7 @@ public partial class CoreConfigV2rayService
|
|||
};
|
||||
v2rayConfig.observatory = observatory;
|
||||
}
|
||||
else if (multipleLoad == EMultipleLoad.LeastLoad)
|
||||
else if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback)
|
||||
{
|
||||
var burstObservatory = new BurstObservatory4Ray
|
||||
{
|
||||
|
|
@ -30,6 +30,11 @@ public partial class CoreConfigV2rayService
|
|||
};
|
||||
v2rayConfig.burstObservatory = burstObservatory;
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<string> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
|
||||
{
|
||||
var strategyType = multipleLoad switch
|
||||
{
|
||||
EMultipleLoad.Random => "random",
|
||||
|
|
@ -38,13 +43,22 @@ public partial class CoreConfigV2rayService
|
|||
EMultipleLoad.LeastLoad => "leastLoad",
|
||||
_ => "roundRobin",
|
||||
};
|
||||
var balancerTag = $"{selector}{Global.BalancerTagSuffix}";
|
||||
var balancer = new BalancersItem4Ray
|
||||
{
|
||||
selector = [Global.ProxyTag],
|
||||
strategy = new() { type = strategyType },
|
||||
tag = $"{Global.ProxyTag}-round",
|
||||
selector = [selector],
|
||||
strategy = new()
|
||||
{
|
||||
type = strategyType,
|
||||
settings = new()
|
||||
{
|
||||
expected = 1,
|
||||
},
|
||||
},
|
||||
tag = balancerTag,
|
||||
};
|
||||
v2rayConfig.routing.balancers = [balancer];
|
||||
return await Task.FromResult(0);
|
||||
v2rayConfig.routing.balancers ??= new();
|
||||
v2rayConfig.routing.balancers.Add(balancer);
|
||||
return await Task.FromResult(balancerTag);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -261,17 +261,7 @@ public partial class CoreConfigV2rayService
|
|||
|
||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||
{
|
||||
var userHostsMap = simpleDNSItem.Hosts
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(line => line.Trim())
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
.Where(parts => parts.Length >= 2)
|
||||
.GroupBy(parts => parts[0])
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||
);
|
||||
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||
|
||||
foreach (var kvp in userHostsMap)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -552,7 +552,7 @@ public partial class CoreConfigV2rayService
|
|||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig)
|
||||
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -577,6 +577,34 @@ public partial class CoreConfigV2rayService
|
|||
{
|
||||
index++;
|
||||
|
||||
if (node.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
||||
{
|
||||
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
|
||||
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var childProfiles = (await Task.WhenAll(
|
||||
Utils.String2List(profileGroupItem.ChildItems)
|
||||
.Where(p => !p.IsNullOrEmpty())
|
||||
.Select(AppManager.Instance.GetProfileItem)
|
||||
)).Where(p => p != null).ToList();
|
||||
if (childProfiles.Count <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var childBaseTagName = $"{baseTagName}-{index}";
|
||||
var ret = node.ConfigType switch
|
||||
{
|
||||
EConfigType.PolicyGroup =>
|
||||
await GenOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
|
||||
EConfigType.ProxyChain =>
|
||||
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle proxy chain
|
||||
string? prevTag = null;
|
||||
var currentOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
|
|
@ -590,7 +618,7 @@ public partial class CoreConfigV2rayService
|
|||
|
||||
// current proxy
|
||||
await GenOutbound(node, currentOutbound);
|
||||
currentOutbound.tag = $"{Global.ProxyTag}-{index}";
|
||||
currentOutbound.tag = $"{baseTagName}-{index}";
|
||||
|
||||
if (!node.Subid.IsNullOrEmpty())
|
||||
{
|
||||
|
|
@ -606,7 +634,7 @@ public partial class CoreConfigV2rayService
|
|||
{
|
||||
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
await GenOutbound(prevNode, prevOutbound);
|
||||
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
|
||||
prevTag = $"prev-{baseTagName}-{++prevIndex}";
|
||||
prevOutbound.tag = prevTag;
|
||||
prevOutbounds.Add(prevOutbound);
|
||||
}
|
||||
|
|
@ -692,4 +720,50 @@ public partial class CoreConfigV2rayService
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<int> GenChainOutboundsList(List<ProfileItem> nodes, V2rayConfig v2RayConfig, string baseTagName = Global.ProxyTag)
|
||||
{
|
||||
var resultOutbounds = new List<Outbounds4Ray>();
|
||||
for (var i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
var node = nodes[i];
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
if (txtOutbound.IsNullOrEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
var result = await GenOutbound(node, outbound);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
outbound.tag = baseTagName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// avoid v2ray observe
|
||||
outbound.tag = "chain-" + baseTagName + i.ToString();
|
||||
}
|
||||
|
||||
if (i != nodes.Count - 1)
|
||||
{
|
||||
outbound.streamSettings.sockopt = new()
|
||||
{
|
||||
dialerProxy = "chain-" + baseTagName + (i + 1).ToString()
|
||||
};
|
||||
}
|
||||
|
||||
resultOutbounds.Add(outbound);
|
||||
}
|
||||
v2RayConfig.outbounds ??= new();
|
||||
resultOutbounds.AddRange(v2RayConfig.outbounds);
|
||||
v2RayConfig.outbounds = resultOutbounds;
|
||||
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,16 +125,60 @@ public partial class CoreConfigV2rayService
|
|||
}
|
||||
|
||||
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||
|
||||
if (node == null
|
||||
|| !Global.XraySupportConfigType.Contains(node.ConfigType))
|
||||
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
|
||||
&& node.ConfigType is not (EConfigType.PolicyGroup or EConfigType.ProxyChain)))
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
var tag = Global.ProxyTag + node.IndexId.ToString();
|
||||
if (v2rayConfig.outbounds.Any(p => p.tag == tag))
|
||||
{
|
||||
return tag;
|
||||
}
|
||||
|
||||
if (node.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
||||
{
|
||||
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
|
||||
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
var childProfiles = (await Task.WhenAll(
|
||||
Utils.String2List(profileGroupItem.ChildItems)
|
||||
.Where(p => !p.IsNullOrEmpty())
|
||||
.Select(AppManager.Instance.GetProfileItem)
|
||||
)).Where(p => p != null).ToList();
|
||||
if (childProfiles.Count <= 0)
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
var childBaseTagName = $"{Global.ProxyTag}-{node.IndexId}";
|
||||
var ret = node.ConfigType switch
|
||||
{
|
||||
EConfigType.PolicyGroup =>
|
||||
await GenOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
|
||||
EConfigType.ProxyChain =>
|
||||
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
if (node.ConfigType == EConfigType.PolicyGroup)
|
||||
{
|
||||
await GenBalancer(v2rayConfig, profileGroupItem.MultipleLoad, childBaseTagName);
|
||||
}
|
||||
if (ret == 0)
|
||||
{
|
||||
return childBaseTagName;
|
||||
}
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
await GenOutbound(node, outbound);
|
||||
outbound.tag = Global.ProxyTag + node.IndexId.ToString();
|
||||
outbound.tag = tag;
|
||||
v2rayConfig.outbounds.Add(outbound);
|
||||
|
||||
return outbound.tag;
|
||||
|
|
|
|||
226
v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs
Normal file
226
v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
using System.Data;
|
||||
using System.Reactive;
|
||||
using DynamicData.Binding;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddGroupServerViewModel : MyReactiveObject
|
||||
{
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedChild { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public IList<ProfileItem> SelectedChildren { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? CoreType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? PolicyGroupType { get; set; }
|
||||
|
||||
public IObservableCollection<ProfileItem> ChildItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
||||
|
||||
//public ReactiveCommand<Unit, Unit> AddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RemoveCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public AddGroupServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
x => x.SelectedChild,
|
||||
SelectedChild => SelectedChild != null && !SelectedChild.Remarks.IsNullOrEmpty());
|
||||
|
||||
RemoveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await ChildRemoveAsync();
|
||||
}, canEditRemove);
|
||||
MoveTopCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await MoveServer(EMove.Top);
|
||||
}, canEditRemove);
|
||||
MoveUpCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await MoveServer(EMove.Up);
|
||||
}, canEditRemove);
|
||||
MoveDownCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await MoveServer(EMove.Down);
|
||||
}, canEditRemove);
|
||||
MoveBottomCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await MoveServer(EMove.Bottom);
|
||||
}, canEditRemove);
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveServerAsync();
|
||||
});
|
||||
|
||||
SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem);
|
||||
|
||||
CoreType = (SelectedSource?.CoreType ?? ECoreType.Xray).ToString();
|
||||
|
||||
ProfileGroupItemManager.Instance.TryGet(profileItem.IndexId, out var profileGroup);
|
||||
PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
|
||||
{
|
||||
EMultipleLoad.LeastPing => ResUI.TbLeastPing,
|
||||
EMultipleLoad.Fallback => ResUI.TbFallback,
|
||||
EMultipleLoad.Random => ResUI.TbRandom,
|
||||
EMultipleLoad.RoundRobin => ResUI.TbRoundRobin,
|
||||
EMultipleLoad.LeastLoad => ResUI.TbLeastLoad,
|
||||
_ => ResUI.TbLeastPing,
|
||||
};
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
var childItemMulti = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource?.IndexId);
|
||||
if (childItemMulti != null)
|
||||
{
|
||||
var childIndexIds = childItemMulti.ChildItems.IsNullOrEmpty() ? new List<string>() : Utils.String2List(childItemMulti.ChildItems);
|
||||
foreach (var item in childIndexIds)
|
||||
{
|
||||
var child = await AppManager.Instance.GetProfileItem(item);
|
||||
if (child == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ChildItemsObs.Add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ChildRemoveAsync()
|
||||
{
|
||||
if (SelectedChild == null || SelectedChild.IndexId.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
return;
|
||||
}
|
||||
foreach (var it in SelectedChildren ?? [SelectedChild])
|
||||
{
|
||||
if (it != null)
|
||||
{
|
||||
ChildItemsObs.Remove(it);
|
||||
}
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task MoveServer(EMove eMove)
|
||||
{
|
||||
if (SelectedChild == null || SelectedChild.IndexId.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||
return;
|
||||
}
|
||||
var index = ChildItemsObs.IndexOf(SelectedChild);
|
||||
if (index < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var selectedChild = JsonUtils.DeepCopy(SelectedChild);
|
||||
switch (eMove)
|
||||
{
|
||||
case EMove.Top:
|
||||
if (index == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ChildItemsObs.RemoveAt(index);
|
||||
ChildItemsObs.Insert(0, selectedChild);
|
||||
break;
|
||||
case EMove.Up:
|
||||
if (index == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ChildItemsObs.RemoveAt(index);
|
||||
ChildItemsObs.Insert(index - 1, selectedChild);
|
||||
break;
|
||||
case EMove.Down:
|
||||
if (index == ChildItemsObs.Count - 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ChildItemsObs.RemoveAt(index);
|
||||
ChildItemsObs.Insert(index + 1, selectedChild);
|
||||
break;
|
||||
case EMove.Bottom:
|
||||
if (index == ChildItemsObs.Count - 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ChildItemsObs.RemoveAt(index);
|
||||
ChildItemsObs.Add(selectedChild);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task SaveServerAsync()
|
||||
{
|
||||
var remarks = SelectedSource.Remarks;
|
||||
if (remarks.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseFillRemarks);
|
||||
return;
|
||||
}
|
||||
if (ChildItemsObs.Count == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseAddAtLeastOneServer);
|
||||
return;
|
||||
}
|
||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? ECoreType.Xray : (ECoreType)Enum.Parse(typeof(ECoreType), CoreType);
|
||||
if (SelectedSource.CoreType is not (ECoreType.Xray or ECoreType.sing_box) ||
|
||||
SelectedSource.ConfigType is not (EConfigType.ProxyChain or EConfigType.PolicyGroup))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var childIndexIds = new List<string>();
|
||||
foreach (var item in ChildItemsObs)
|
||||
{
|
||||
if (!item.IndexId.IsNullOrEmpty())
|
||||
{
|
||||
childIndexIds.Add(item.IndexId);
|
||||
}
|
||||
}
|
||||
SelectedSource.Address = Utils.List2String(childIndexIds);
|
||||
var profileGroup = ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(SelectedSource.IndexId);
|
||||
profileGroup.ChildItems = Utils.List2String(childIndexIds);
|
||||
profileGroup.MultipleLoad = PolicyGroupType switch
|
||||
{
|
||||
var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing,
|
||||
var s when s == ResUI.TbFallback => EMultipleLoad.Fallback,
|
||||
var s when s == ResUI.TbRandom => EMultipleLoad.Random,
|
||||
var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin,
|
||||
var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad,
|
||||
_ => EMultipleLoad.LeastPing,
|
||||
};
|
||||
if (await ConfigHandler.AddGroupServerCommon(_config, SelectedSource, profileGroup, true) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +63,16 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||
|
||||
private CheckUpdateModel GetCheckUpdateModel(string coreType)
|
||||
{
|
||||
if (coreType == _v2rayN && Utils.IsPackagedInstall())
|
||||
{
|
||||
return new()
|
||||
{
|
||||
IsSelected = false,
|
||||
CoreType = coreType,
|
||||
Remarks = ResUI.menuCheckUpdate + " (Not Support)",
|
||||
};
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType) ?? true,
|
||||
|
|
@ -104,6 +114,11 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||
}
|
||||
else if (item.CoreType == _v2rayN)
|
||||
{
|
||||
if (Utils.IsPackagedInstall())
|
||||
{
|
||||
await UpdateView(_v2rayN, "Not Support");
|
||||
continue;
|
||||
}
|
||||
await CheckUpdateN(EnableCheckPreReleaseUpdate);
|
||||
}
|
||||
else if (item.CoreType == ECoreType.Xray.ToString())
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddAnytlsServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddPolicyGroupServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddProxyChainServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaImageCmd { get; }
|
||||
|
|
@ -122,6 +124,14 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
{
|
||||
await AddServerAsync(true, EConfigType.Custom);
|
||||
});
|
||||
AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddServerAsync(true, EConfigType.PolicyGroup);
|
||||
});
|
||||
AddProxyChainServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddServerAsync(true, EConfigType.ProxyChain);
|
||||
});
|
||||
AddServerViaClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddServerViaClipboardAsync(null);
|
||||
|
|
@ -228,6 +238,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
await ConfigHandler.InitBuiltinDNS(_config);
|
||||
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
|
||||
await ProfileExManager.Instance.Init();
|
||||
await ProfileGroupItemManager.Instance.Init();
|
||||
await CoreManager.Instance.Init(_config, UpdateHandler);
|
||||
TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);
|
||||
|
||||
|
|
@ -322,6 +333,10 @@ public class MainWindowViewModel : MyReactiveObject
|
|||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item);
|
||||
}
|
||||
else if (eConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddGroupServerWindow, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item);
|
||||
|
|
|
|||
|
|
@ -53,11 +53,13 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
public ReactiveCommand<Unit, Unit> CopyServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SetDefaultServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ShareServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SetDefaultMultipleServerXrayRandomCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SetDefaultMultipleServerXrayRoundRobinCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SetDefaultMultipleServerXrayLeastPingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SetDefaultMultipleServerXrayLeastLoadCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SetDefaultMultipleServerSingBoxLeastPingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayRandomCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayRoundRobinCmd { 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
|
||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
||||
|
|
@ -139,25 +141,33 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
{
|
||||
await ShareServerAsync();
|
||||
}, canEditRemove);
|
||||
SetDefaultMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
GenGroupMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.Random);
|
||||
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Random);
|
||||
}, canEditRemove);
|
||||
SetDefaultMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
GenGroupMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin);
|
||||
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin);
|
||||
}, canEditRemove);
|
||||
SetDefaultMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
GenGroupMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing);
|
||||
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing);
|
||||
}, canEditRemove);
|
||||
SetDefaultMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
GenGroupMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad);
|
||||
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad);
|
||||
}, canEditRemove);
|
||||
SetDefaultMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
GenGroupMultipleServerXrayFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SetDefaultMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing);
|
||||
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);
|
||||
|
||||
//servers move
|
||||
|
|
@ -491,6 +501,10 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item);
|
||||
}
|
||||
else if (eConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddGroupServerWindow, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item);
|
||||
|
|
@ -606,7 +620,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
await _updateView?.Invoke(EViewAction.ShareServer, url);
|
||||
}
|
||||
|
||||
private async Task SetDefaultMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad)
|
||||
private async Task GenGroupMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad)
|
||||
{
|
||||
var lstSelected = await GetProfileItems(true);
|
||||
if (lstSelected == null)
|
||||
|
|
@ -614,7 +628,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||
return;
|
||||
}
|
||||
|
||||
var ret = await ConfigHandler.AddCustomServer4Multiple(_config, lstSelected, coreType, multipleLoad);
|
||||
var ret = await ConfigHandler.AddGroupServer4Multiple(_config, lstSelected, coreType, multipleLoad, SelectedSub?.Id);
|
||||
if (ret.Success != true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
|
|
|
|||
151
v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml
Normal file
151
v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<Window
|
||||
x:Class="v2rayN.Desktop.Views.AddGroupServerWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Title="{x:Static resx:ResUI.menuServers}"
|
||||
Width="900"
|
||||
Height="700"
|
||||
x:DataType="vms:AddGroupServerViewModel"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Center"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid DockPanel.Dock="Top" RowDefinitions="Auto,*,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
ColumnDefinitions="180,Auto,Auto"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Text="{x:Static resx:ResUI.menuServers}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbRemarks}" />
|
||||
<TextBox
|
||||
x:Name="txtRemarks"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbCoreType}" />
|
||||
<ComboBox
|
||||
x:Name="cmbCoreType"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<Grid
|
||||
x:Name="gridPolicyGroup"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
ColumnDefinitions="180,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPolicyGroupType}" />
|
||||
<ComboBox
|
||||
x:Name="cmbPolicyGroupType"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<TabControl>
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
|
||||
<DataGrid
|
||||
x:Name="lstChild"
|
||||
Grid.Row="1"
|
||||
AutoGenerateColumns="False"
|
||||
Background="Transparent"
|
||||
BorderThickness="1"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="False"
|
||||
GridLinesVisibility="All"
|
||||
HeadersVisibility="Column"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding ChildItemsObs}"
|
||||
SelectionMode="Extended">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem x:Name="menuAddChildServer" Header="{x:Static resx:ResUI.menuAddChildServer}" />
|
||||
<MenuItem x:Name="menuRemoveChildServer" Header="{x:Static resx:ResUI.menuRemoveChildServer}" />
|
||||
<MenuItem x:Name="menuSelectAllChild" Header="{x:Static resx:ResUI.menuSelectAll}" />
|
||||
<Separator />
|
||||
<MenuItem x:Name="menuMoveTop" Header="{x:Static resx:ResUI.menuMoveTop}" />
|
||||
<MenuItem x:Name="menuMoveUp" Header="{x:Static resx:ResUI.menuMoveUp}" />
|
||||
<MenuItem x:Name="menuMoveDown" Header="{x:Static resx:ResUI.menuMoveDown}" />
|
||||
<MenuItem x:Name="menuMoveBottom" Header="{x:Static resx:ResUI.menuMoveBottom}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="150"
|
||||
Binding="{Binding ConfigType}"
|
||||
Header="{x:Static resx:ResUI.LvServiceType}" />
|
||||
<DataGridTextColumn
|
||||
Width="150"
|
||||
Binding="{Binding Remarks}"
|
||||
Header="{x:Static resx:ResUI.LvRemarks}" />
|
||||
<DataGridTextColumn
|
||||
Width="120"
|
||||
Binding="{Binding Address}"
|
||||
Header="{x:Static resx:ResUI.LvAddress}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding Port}"
|
||||
Header="{x:Static resx:ResUI.LvPort}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding Network}"
|
||||
Header="{x:Static resx:ResUI.LvTransportProtocol}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding StreamSecurity}"
|
||||
Header="{x:Static resx:ResUI.LvTLS}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
166
v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs
Normal file
166
v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
using System.Reactive.Disposables;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using DynamicData;
|
||||
using ReactiveUI;
|
||||
using v2rayN.Desktop.Base;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
{
|
||||
public AddGroupServerWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public AddGroupServerWindow(ProfileItem profileItem)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.Loaded += Window_Loaded;
|
||||
btnCancel.Click += (s, e) => this.Close();
|
||||
lstChild.SelectionChanged += LstChild_SelectionChanged;
|
||||
|
||||
ViewModel = new AddGroupServerViewModel(profileItem, UpdateViewHandler);
|
||||
|
||||
cmbCoreType.ItemsSource = Global.CoreTypes;
|
||||
cmbPolicyGroupType.ItemsSource = new List<string>
|
||||
{
|
||||
ResUI.TbLeastPing,
|
||||
ResUI.TbFallback,
|
||||
ResUI.TbRandom,
|
||||
ResUI.TbRoundRobin,
|
||||
ResUI.TbLeastLoad,
|
||||
};
|
||||
|
||||
switch (profileItem.ConfigType)
|
||||
{
|
||||
case EConfigType.PolicyGroup:
|
||||
this.Title = ResUI.TbConfigTypePolicyGroup;
|
||||
break;
|
||||
case EConfigType.ProxyChain:
|
||||
this.Title = ResUI.TbConfigTypeProxyChain;
|
||||
gridPolicyGroup.IsVisible = false;
|
||||
break;
|
||||
}
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).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.BindCommand(ViewModel, vm => vm.RemoveCmd, v => v.menuRemoveChildServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveDownCmd, v => v.menuMoveDown).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveBottomCmd, v => v.menuMoveBottom).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
// Context menu actions that require custom logic (Add, SelectAll)
|
||||
menuAddChildServer.Click += MenuAddChild_Click;
|
||||
menuSelectAllChild.Click += (s, e) => lstChild.SelectAll();
|
||||
|
||||
// Keyboard shortcuts when focus is within grid
|
||||
this.AddHandler(KeyDownEvent, AddGroupServerWindow_KeyDown, RoutingStrategies.Tunnel);
|
||||
lstChild.LoadingRow += LstChild_LoadingRow;
|
||||
}
|
||||
|
||||
private void LstChild_LoadingRow(object? sender, DataGridRowEventArgs e)
|
||||
{
|
||||
e.Row.Header = $" {e.Row.Index + 1}";
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case EViewAction.CloseWindow:
|
||||
this.Close(true);
|
||||
break;
|
||||
}
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
txtRemarks.Focus();
|
||||
}
|
||||
|
||||
private void AddGroupServerWindow_KeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (!lstChild.IsKeyboardFocusWithin)
|
||||
return;
|
||||
|
||||
if ((e.KeyModifiers & (KeyModifiers.Control | KeyModifiers.Meta)) != 0)
|
||||
{
|
||||
if (e.Key == Key.A)
|
||||
{
|
||||
lstChild.SelectAll();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.T:
|
||||
ViewModel?.MoveServer(EMove.Top);
|
||||
e.Handled = true;
|
||||
break;
|
||||
case Key.U:
|
||||
ViewModel?.MoveServer(EMove.Up);
|
||||
e.Handled = true;
|
||||
break;
|
||||
case Key.D:
|
||||
ViewModel?.MoveServer(EMove.Down);
|
||||
e.Handled = true;
|
||||
break;
|
||||
case Key.B:
|
||||
ViewModel?.MoveServer(EMove.Bottom);
|
||||
e.Handled = true;
|
||||
break;
|
||||
case Key.Delete:
|
||||
ViewModel?.ChildRemoveAsync();
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void MenuAddChild_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectWindow = new ProfilesSelectWindow();
|
||||
if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup)
|
||||
{
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
|
||||
}
|
||||
selectWindow.AllowMultiSelect(true);
|
||||
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||
if (result == true)
|
||||
{
|
||||
var profiles = await selectWindow.ProfileItems;
|
||||
ViewModel?.ChildItemsObs.AddRange(profiles);
|
||||
}
|
||||
}
|
||||
|
||||
private void LstChild_SelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel != null)
|
||||
{
|
||||
ViewModel.SelectedChildren = lstChild.SelectedItems.Cast<ProfileItem>().ToList();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -229,7 +229,7 @@
|
|||
Grid.Column="2"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbApplyProxyDomainsOnly}"
|
||||
Text="{x:Static resx:ResUI.TbFakeIPTips}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@
|
|||
<MenuItem x:Name="menuAddServerViaScan" Header="{x:Static resx:ResUI.menuAddServerViaScan}" />
|
||||
<MenuItem x:Name="menuAddServerViaImage" Header="{x:Static resx:ResUI.menuAddServerViaImage}" />
|
||||
<MenuItem x:Name="menuAddCustomServer" Header="{x:Static resx:ResUI.menuAddCustomServer}" />
|
||||
<MenuItem x:Name="menuAddPolicyGroupServer" Header="{x:Static resx:ResUI.menuAddPolicyGroupServer}" />
|
||||
<MenuItem x:Name="menuAddProxyChainServer" Header="{x:Static resx:ResUI.menuAddProxyChainServer}" />
|
||||
<Separator />
|
||||
<MenuItem x:Name="menuAddVmessServer" Header="{x:Static resx:ResUI.menuAddVmessServer}" />
|
||||
<MenuItem x:Name="menuAddVlessServer" Header="{x:Static resx:ResUI.menuAddVlessServer}" />
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddPolicyGroupServerCmd, v => v.menuAddPolicyGroupServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddProxyChainServerCmd, v => v.menuAddProxyChainServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddServerViaImageCmd, v => v.menuAddServerViaImage).DisposeWith(disposables);
|
||||
|
|
@ -204,6 +206,11 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||
return false;
|
||||
return await new AddServer2Window((ProfileItem)obj).ShowDialog<bool>(this);
|
||||
|
||||
case EViewAction.AddGroupServerWindow:
|
||||
if (obj is null)
|
||||
return false;
|
||||
return await new AddGroupServerWindow((ProfileItem)obj).ShowDialog<bool>(this);
|
||||
|
||||
case EViewAction.DNSSettingWindow:
|
||||
return await new DNSSettingWindow().ShowDialog<bool>(this);
|
||||
|
||||
|
|
|
|||
|
|
@ -99,13 +99,14 @@
|
|||
<MenuItem x:Name="menuCopyServer" Header="{x:Static resx:ResUI.menuCopyServer}" />
|
||||
<MenuItem x:Name="menuShareServer" Header="{x:Static resx:ResUI.menuShareServer}" />
|
||||
<Separator />
|
||||
<MenuItem Header="{x:Static resx:ResUI.menuSetDefaultMultipleServer}">
|
||||
<MenuItem x:Name="menuSetDefaultMultipleServerXrayRandom" Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayRandom}" />
|
||||
<MenuItem x:Name="menuSetDefaultMultipleServerXrayRoundRobin" Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayRoundRobin}" />
|
||||
<MenuItem x:Name="menuSetDefaultMultipleServerXrayLeastPing" Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayLeastPing}" />
|
||||
<MenuItem x:Name="menuSetDefaultMultipleServerXrayLeastLoad" Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayLeastLoad}" />
|
||||
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupMultipleServer}">
|
||||
<MenuItem x:Name="menuGenGroupMultipleServerXrayRandom" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRandom}" />
|
||||
<MenuItem x:Name="menuGenGroupMultipleServerXrayRoundRobin" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRoundRobin}" />
|
||||
<MenuItem x:Name="menuGenGroupMultipleServerXrayLeastPing" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastPing}" />
|
||||
<MenuItem x:Name="menuGenGroupMultipleServerXrayLeastLoad" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastLoad}" />
|
||||
<Separator />
|
||||
<MenuItem x:Name="menuSetDefaultMultipleServerSingBoxLeastPing" Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerSingBoxLeastPing}" />
|
||||
<MenuItem x:Name="menuGenGroupMultipleServerSingBoxLeastPing" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" />
|
||||
<MenuItem x:Name="menuGenGroupMultipleServerSingBoxFallback" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxFallback}" />
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem x:Name="menuMixedTestServer" Header="{x:Static resx:ResUI.menuMixedTestServer}" />
|
||||
|
|
|
|||
|
|
@ -69,11 +69,12 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||
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.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerXrayRandomCmd, v => v.menuSetDefaultMultipleServerXrayRandom).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerXrayRoundRobinCmd, v => v.menuSetDefaultMultipleServerXrayRoundRobin).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerXrayLeastPingCmd, v => v.menuSetDefaultMultipleServerXrayLeastPing).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerXrayLeastLoadCmd, v => v.menuSetDefaultMultipleServerXrayLeastLoad).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerSingBoxLeastPingCmd, v => v.menuSetDefaultMultipleServerSingBoxLeastPing).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRandomCmd, v => v.menuGenGroupMultipleServerXrayRandom).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRoundRobinCmd, v => v.menuGenGroupMultipleServerXrayRoundRobin).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
|
||||
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
|
||||
|
|
@ -170,6 +171,11 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||
return false;
|
||||
return await new AddServer2Window((ProfileItem)obj).ShowDialog<bool>(_window);
|
||||
|
||||
case EViewAction.AddGroupServerWindow:
|
||||
if (obj is null)
|
||||
return false;
|
||||
return await new AddGroupServerWindow((ProfileItem)obj).ShowDialog<bool>(_window);
|
||||
|
||||
case EViewAction.ShareServer:
|
||||
if (obj is null)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
|
|||
private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectWindow = new ProfilesSelectWindow();
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
|
||||
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||
if (result == true)
|
||||
{
|
||||
|
|
@ -78,7 +78,7 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
|
|||
private async void BtnSelectNextProfile_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectWindow = new ProfilesSelectWindow();
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
|
||||
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||
if (result == true)
|
||||
{
|
||||
|
|
|
|||
213
v2rayN/v2rayN/Views/AddGroupServerWindow.xaml
Normal file
213
v2rayN/v2rayN/Views/AddGroupServerWindow.xaml
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
<base:WindowBase
|
||||
x:Class="v2rayN.Views.AddGroupServerWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:v2rayN.Base"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:reactiveui="http://reactiveui.net"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Title="{x:Static resx:ResUI.menuServers}"
|
||||
Width="900"
|
||||
Height="700"
|
||||
x:TypeArguments="vms:AddGroupServerViewModel"
|
||||
ResizeMode="CanResize"
|
||||
ShowInTaskbar="False"
|
||||
Style="{StaticResource WindowGlobal}"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Center"
|
||||
DockPanel.Dock="Bottom"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
Style="{StaticResource DefButton}" />
|
||||
</StackPanel>
|
||||
<Grid DockPanel.Dock="Top">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource ModuleTitle}"
|
||||
Text="{x:Static resx:ResUI.menuServers}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbRemarks}" />
|
||||
<TextBox
|
||||
x:Name="txtRemarks"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbCoreType}" />
|
||||
<ComboBox
|
||||
x:Name="cmbCoreType"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.TbCoreType}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
|
||||
<Grid
|
||||
x:Name="gridPolicyGroup"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbPolicyGroupType}" />
|
||||
<ComboBox
|
||||
x:Name="cmbPolicyGroupType"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.TbPolicyGroupType}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<TabControl>
|
||||
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
|
||||
<DataGrid
|
||||
x:Name="lstChild"
|
||||
AutoGenerateColumns="False"
|
||||
BorderThickness="1"
|
||||
CanUserAddRows="False"
|
||||
CanUserResizeRows="False"
|
||||
CanUserSortColumns="False"
|
||||
EnableRowVirtualization="True"
|
||||
GridLinesVisibility="All"
|
||||
HeadersVisibility="Column"
|
||||
IsReadOnly="True"
|
||||
Style="{StaticResource DefDataGrid}">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu Style="{StaticResource DefContextMenu}">
|
||||
<MenuItem
|
||||
x:Name="menuAddChildServer"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Click="MenuAddChild_Click"
|
||||
Header="{x:Static resx:ResUI.menuAddChildServer}" />
|
||||
<MenuItem
|
||||
x:Name="menuRemoveChildServer"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuRemoveChildServer}" />
|
||||
<MenuItem
|
||||
x:Name="menuSelectAllChild"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSelectAll}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
x:Name="menuMoveTop"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuMoveTop}" />
|
||||
<MenuItem
|
||||
x:Name="menuMoveUp"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuMoveUp}" />
|
||||
<MenuItem
|
||||
x:Name="menuMoveDown"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuMoveDown}" />
|
||||
<MenuItem
|
||||
x:Name="menuMoveBottom"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuMoveBottom}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="150"
|
||||
Binding="{Binding ConfigType}"
|
||||
Header="{x:Static resx:ResUI.LvServiceType}" />
|
||||
<DataGridTextColumn
|
||||
Width="150"
|
||||
Binding="{Binding Remarks}"
|
||||
Header="{x:Static resx:ResUI.LvRemarks}" />
|
||||
<DataGridTextColumn
|
||||
Width="120"
|
||||
Binding="{Binding Address}"
|
||||
Header="{x:Static resx:ResUI.LvAddress}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding Port}"
|
||||
Header="{x:Static resx:ResUI.LvPort}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding Network}"
|
||||
Header="{x:Static resx:ResUI.LvTransportProtocol}" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
Binding="{Binding StreamSecurity}"
|
||||
Header="{x:Static resx:ResUI.LvTLS}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</base:WindowBase>
|
||||
147
v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs
Normal file
147
v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
using DynamicData;
|
||||
|
||||
namespace v2rayN.Views;
|
||||
|
||||
public partial class AddGroupServerWindow
|
||||
{
|
||||
public AddGroupServerWindow(ProfileItem profileItem)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.Owner = Application.Current.MainWindow;
|
||||
this.Loaded += Window_Loaded;
|
||||
this.PreviewKeyDown += AddGroupServerWindow_PreviewKeyDown;
|
||||
lstChild.SelectionChanged += LstChild_SelectionChanged;
|
||||
menuSelectAllChild.Click += MenuSelectAllChild_Click;
|
||||
|
||||
ViewModel = new AddGroupServerViewModel(profileItem, UpdateViewHandler);
|
||||
|
||||
cmbCoreType.ItemsSource = Global.CoreTypes;
|
||||
cmbPolicyGroupType.ItemsSource = new List<string>
|
||||
{
|
||||
ResUI.TbLeastPing,
|
||||
ResUI.TbFallback,
|
||||
ResUI.TbRandom,
|
||||
ResUI.TbRoundRobin,
|
||||
ResUI.TbLeastLoad,
|
||||
};
|
||||
|
||||
switch (profileItem.ConfigType)
|
||||
{
|
||||
case EConfigType.PolicyGroup:
|
||||
this.Title = ResUI.TbConfigTypePolicyGroup;
|
||||
break;
|
||||
|
||||
case EConfigType.ProxyChain:
|
||||
this.Title = ResUI.TbConfigTypeProxyChain;
|
||||
gridPolicyGroup.Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
}
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.Text).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.BindCommand(ViewModel, vm => vm.RemoveCmd, v => v.menuRemoveChildServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveDownCmd, v => v.menuMoveDown).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveBottomCmd, v => v.menuMoveBottom).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case EViewAction.CloseWindow:
|
||||
this.DialogResult = true;
|
||||
break;
|
||||
}
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
txtRemarks.Focus();
|
||||
}
|
||||
|
||||
private void AddGroupServerWindow_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (!lstChild.IsKeyboardFocusWithin)
|
||||
return;
|
||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
{
|
||||
if (e.Key == Key.A)
|
||||
{
|
||||
lstChild.SelectAll();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (e.Key == Key.T)
|
||||
{
|
||||
ViewModel?.MoveServer(EMove.Top);
|
||||
}
|
||||
else if (e.Key == Key.U)
|
||||
{
|
||||
ViewModel?.MoveServer(EMove.Up);
|
||||
}
|
||||
else if (e.Key == Key.D)
|
||||
{
|
||||
ViewModel?.MoveServer(EMove.Down);
|
||||
}
|
||||
else if (e.Key == Key.B)
|
||||
{
|
||||
ViewModel?.MoveServer(EMove.Bottom);
|
||||
}
|
||||
else if (e.Key == Key.Delete)
|
||||
{
|
||||
ViewModel?.ChildRemoveAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void MenuAddChild_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectWindow = new ProfilesSelectWindow();
|
||||
if (ViewModel?.SelectedSource?.ConfigType == EConfigType.PolicyGroup)
|
||||
{
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
|
||||
}
|
||||
selectWindow.AllowMultiSelect(true);
|
||||
if (selectWindow.ShowDialog() == true)
|
||||
{
|
||||
var profiles = await selectWindow.ProfileItems;
|
||||
ViewModel?.ChildItemsObs.AddRange(profiles);
|
||||
}
|
||||
}
|
||||
|
||||
private void LstChild_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel != null)
|
||||
{
|
||||
ViewModel.SelectedChildren = lstChild.SelectedItems.Cast<ProfileItem>().ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private void MenuSelectAllChild_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
lstChild.SelectAll();
|
||||
}
|
||||
}
|
||||
|
|
@ -281,7 +281,7 @@
|
|||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbApplyProxyDomainsOnly}"
|
||||
Text="{x:Static resx:ResUI.TbFakeIPTips}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@
|
|||
x:Name="menuAddCustomServer"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuAddCustomServer}" />
|
||||
<MenuItem
|
||||
x:Name="menuAddPolicyGroupServer"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuAddPolicyGroupServer}" />
|
||||
<MenuItem
|
||||
x:Name="menuAddProxyChainServer"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuAddProxyChainServer}" />
|
||||
<Separator Margin="-40,5" />
|
||||
<MenuItem
|
||||
x:Name="menuAddVmessServer"
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ public partial class MainWindow
|
|||
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddPolicyGroupServerCmd, v => v.menuAddPolicyGroupServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddProxyChainServerCmd, v => v.menuAddProxyChainServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddServerViaImageCmd, v => v.menuAddServerViaImage).DisposeWith(disposables);
|
||||
|
|
@ -193,6 +195,11 @@ public partial class MainWindow
|
|||
return false;
|
||||
return (new AddServer2Window((ProfileItem)obj)).ShowDialog() ?? false;
|
||||
|
||||
case EViewAction.AddGroupServerWindow:
|
||||
if (obj is null)
|
||||
return false;
|
||||
return (new AddGroupServerWindow((ProfileItem)obj)).ShowDialog() ?? false;
|
||||
|
||||
case EViewAction.DNSSettingWindow:
|
||||
return (new DNSSettingWindow().ShowDialog() ?? false);
|
||||
|
||||
|
|
|
|||
|
|
@ -125,28 +125,32 @@
|
|||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuShareServer}" />
|
||||
<Separator />
|
||||
<MenuItem Header="{x:Static resx:ResUI.menuSetDefaultMultipleServer}">
|
||||
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupMultipleServer}">
|
||||
<MenuItem
|
||||
x:Name="menuSetDefaultMultipleServerXrayRandom"
|
||||
x:Name="menuGenGroupMultipleServerXrayRandom"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayRandom}" />
|
||||
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRandom}" />
|
||||
<MenuItem
|
||||
x:Name="menuSetDefaultMultipleServerXrayRoundRobin"
|
||||
x:Name="menuGenGroupMultipleServerXrayRoundRobin"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayRoundRobin}" />
|
||||
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRoundRobin}" />
|
||||
<MenuItem
|
||||
x:Name="menuSetDefaultMultipleServerXrayLeastPing"
|
||||
x:Name="menuGenGroupMultipleServerXrayLeastPing"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayLeastPing}" />
|
||||
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastPing}" />
|
||||
<MenuItem
|
||||
x:Name="menuSetDefaultMultipleServerXrayLeastLoad"
|
||||
x:Name="menuGenGroupMultipleServerXrayLeastLoad"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayLeastLoad}" />
|
||||
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastLoad}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
x:Name="menuSetDefaultMultipleServerSingBoxLeastPing"
|
||||
x:Name="menuGenGroupMultipleServerSingBoxLeastPing"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerSingBoxLeastPing}" />
|
||||
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" />
|
||||
<MenuItem
|
||||
x:Name="menuGenGroupMultipleServerSingBoxFallback"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxFallback}" />
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem
|
||||
|
|
|
|||
|
|
@ -63,11 +63,12 @@ public partial class ProfilesView
|
|||
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.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerXrayRandomCmd, v => v.menuSetDefaultMultipleServerXrayRandom).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerXrayRoundRobinCmd, v => v.menuSetDefaultMultipleServerXrayRoundRobin).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerXrayLeastPingCmd, v => v.menuSetDefaultMultipleServerXrayLeastPing).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerXrayLeastLoadCmd, v => v.menuSetDefaultMultipleServerXrayLeastLoad).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerSingBoxLeastPingCmd, v => v.menuSetDefaultMultipleServerSingBoxLeastPing).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRandomCmd, v => v.menuGenGroupMultipleServerXrayRandom).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayRoundRobinCmd, v => v.menuGenGroupMultipleServerXrayRoundRobin).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
|
||||
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
|
||||
|
|
@ -151,6 +152,11 @@ public partial class ProfilesView
|
|||
return false;
|
||||
return (new AddServer2Window((ProfileItem)obj)).ShowDialog() ?? false;
|
||||
|
||||
case EViewAction.AddGroupServerWindow:
|
||||
if (obj is null)
|
||||
return false;
|
||||
return (new AddGroupServerWindow((ProfileItem)obj)).ShowDialog() ?? false;
|
||||
|
||||
case EViewAction.ShareServer:
|
||||
if (obj is null)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public partial class SubEditWindow
|
|||
private async void BtnSelectPrevProfile_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectWindow = new ProfilesSelectWindow();
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
|
||||
if (selectWindow.ShowDialog() == true)
|
||||
{
|
||||
var profile = await selectWindow.ProfileItem;
|
||||
|
|
@ -72,7 +72,7 @@ public partial class SubEditWindow
|
|||
private async void BtnSelectNextProfile_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectWindow = new ProfilesSelectWindow();
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
|
||||
if (selectWindow.ShowDialog() == true)
|
||||
{
|
||||
var profile = await selectWindow.ProfileItem;
|
||||
|
|
|
|||
Loading…
Reference in a new issue