mirror of
https://github.com/2dust/v2rayN.git
synced 2026-10-31 01:24:59 +03:00
Adjust Clash API (#10088)
This commit is contained in:
@@ -2,7 +2,14 @@ namespace ServiceLib.Base;
|
|||||||
|
|
||||||
public class BulkObservableCollection<T> : ObservableCollection<T>
|
public class BulkObservableCollection<T> : ObservableCollection<T>
|
||||||
{
|
{
|
||||||
private bool _suppressNotification = false;
|
private bool _suppressNotification;
|
||||||
|
|
||||||
|
public BulkObservableCollection()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkObservableCollection(IEnumerable<T> collection) : base(collection) { }
|
||||||
|
public BulkObservableCollection(List<T> list) : base(list) { }
|
||||||
|
|
||||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -63,4 +70,29 @@ public class BulkObservableCollection<T> : ObservableCollection<T>
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReplaceRange(IEnumerable<T>? collection)
|
||||||
|
{
|
||||||
|
if (collection == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_suppressNotification = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Items.Clear();
|
||||||
|
foreach (var item in collection)
|
||||||
|
{
|
||||||
|
Items.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_suppressNotification = false;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,4 @@ public enum ERuleMode
|
|||||||
Rule = 0,
|
Rule = 0,
|
||||||
Global = 1,
|
Global = 1,
|
||||||
Direct = 2,
|
Direct = 2,
|
||||||
Unchanged = 3
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ global using System.Net.NetworkInformation;
|
|||||||
global using System.Net.Sockets;
|
global using System.Net.Sockets;
|
||||||
global using ReactiveUI.Primitives;
|
global using ReactiveUI.Primitives;
|
||||||
global using ReactiveUI.Primitives.Concurrency;
|
global using ReactiveUI.Primitives.Concurrency;
|
||||||
|
global using ReactiveUI.Primitives.Extensions;
|
||||||
global using ReactiveUI.Primitives.Disposables;
|
global using ReactiveUI.Primitives.Disposables;
|
||||||
global using ReactiveUI.Primitives.Signals;
|
global using ReactiveUI.Primitives.Signals;
|
||||||
global using System.Reflection;
|
global using System.Reflection;
|
||||||
@@ -41,5 +42,3 @@ global using ServiceLib.Services;
|
|||||||
global using ServiceLib.Services.CoreConfig;
|
global using ServiceLib.Services.CoreConfig;
|
||||||
global using ServiceLib.Services.Statistics;
|
global using ServiceLib.Services.Statistics;
|
||||||
global using SQLite;
|
global using SQLite;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,113 +1,104 @@
|
|||||||
using static ServiceLib.Models.Dto.ClashProxies;
|
|
||||||
|
|
||||||
namespace ServiceLib.Manager;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public sealed class ClashApiManager
|
public sealed class ClashApiManager
|
||||||
{
|
{
|
||||||
|
private const string _tag = "ClashApiManager";
|
||||||
private static readonly Lazy<ClashApiManager> instance = new(() => new());
|
private static readonly Lazy<ClashApiManager> instance = new(() => new());
|
||||||
public static ClashApiManager Instance => instance.Value;
|
public static ClashApiManager Instance => instance.Value;
|
||||||
|
|
||||||
private static readonly string _tag = "ClashApiHandler";
|
private static string ApiUrl => $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort2}";
|
||||||
private Dictionary<string, ProxiesItem>? _proxies;
|
|
||||||
public Dictionary<string, object> ProfileContent { get; set; }
|
|
||||||
|
|
||||||
public async Task<Tuple<ClashProxies, ClashProviders>?> GetClashProxiesAsync()
|
public async Task<ClashItem?> GetProxies()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 3; i++)
|
for (var i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
var url = $"{GetApiUrl()}/proxies";
|
var url = $"{ApiUrl}/proxies";
|
||||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
var resultTask = HttpClientHelper.Instance.TryGetAsync(url);
|
||||||
var clashProxies = JsonUtils.Deserialize<ClashProxies>(result);
|
|
||||||
|
|
||||||
var url2 = $"{GetApiUrl()}/providers/proxies";
|
var url2 = $"{ApiUrl}/providers/proxies";
|
||||||
var result2 = await HttpClientHelper.Instance.TryGetAsync(url2);
|
var result2Task = HttpClientHelper.Instance.TryGetAsync(url2);
|
||||||
|
|
||||||
|
await Task.WhenAll(resultTask, result2Task);
|
||||||
|
|
||||||
|
var result = await resultTask;
|
||||||
|
var result2 = await result2Task;
|
||||||
|
var clashProxies = JsonUtils.Deserialize<ClashProxies>(result);
|
||||||
var clashProviders = JsonUtils.Deserialize<ClashProviders>(result2);
|
var clashProviders = JsonUtils.Deserialize<ClashProviders>(result2);
|
||||||
|
|
||||||
if (clashProxies != null || clashProviders != null)
|
if (clashProxies is null && clashProviders is null)
|
||||||
{
|
{
|
||||||
_proxies = clashProxies?.proxies;
|
await Task.Delay(2000);
|
||||||
return new Tuple<ClashProxies, ClashProviders>(clashProxies, clashProviders);
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(2000);
|
var item = new ClashItem();
|
||||||
|
if (clashProxies?.proxies is { Count: > 0 } proxies)
|
||||||
|
{
|
||||||
|
item = item with { Proxies = proxies };
|
||||||
|
}
|
||||||
|
if (clashProviders.providers is { Count: > 0 } providers)
|
||||||
|
{
|
||||||
|
foreach (var provider in providers)
|
||||||
|
{
|
||||||
|
foreach (var proxy in provider.Value.proxies ?? [])
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(proxy.name)
|
||||||
|
|| !item.Proxies.TryAdd(proxy.name, proxy))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
item.ProviderIndexMap.Add(proxy.name, provider.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClashProxiesDelayTest(bool blAll, List<ClashProxyModel> lstProxy, Func<ClashProxyModel?, string, Task> updateFunc)
|
public async Task<int> TestDelay(string name, ClashItem? clashItem)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
if (clashItem?.ProviderIndexMap.TryGetValue(name, out var providerName) == true)
|
||||||
{
|
{
|
||||||
if (blAll)
|
return await TestProviderProxyDelay(name, providerName);
|
||||||
{
|
}
|
||||||
if (_proxies == null)
|
return await TestProxyDelay(name);
|
||||||
{
|
|
||||||
await GetClashProxiesAsync();
|
|
||||||
}
|
|
||||||
lstProxy = [];
|
|
||||||
lstProxy.AddRange(from kv in _proxies ?? []
|
|
||||||
where !Global.notAllowTestType.Contains(kv.Value.type?.ToLower())
|
|
||||||
select new ClashProxyModel()
|
|
||||||
{
|
|
||||||
Name = kv.Value.name,
|
|
||||||
Type = kv.Value.type?.ToLower(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lstProxy is not { Count: > 0 })
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var urlBase = $"{GetApiUrl()}/proxies";
|
|
||||||
urlBase += @"/{0}/delay?timeout=10000&url=" + AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
|
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
|
||||||
foreach (var it in lstProxy)
|
|
||||||
{
|
|
||||||
if (Global.notAllowTestType.Contains(it.Type.ToLower()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var name = it.Name;
|
|
||||||
var url = string.Format(urlBase, name);
|
|
||||||
tasks.Add(Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
|
||||||
await updateFunc?.Invoke(it, result);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
await Task.Delay(1000);
|
|
||||||
await updateFunc?.Invoke(null, "");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ProxiesItem>? GetClashProxyGroups()
|
private async Task<int> TestProxyDelay(string name)
|
||||||
|
{
|
||||||
|
var url = $"{ApiUrl}/proxies/{Utils.UrlEncode(name)}/delay?timeout=10000&url=" +
|
||||||
|
Utils.UrlEncode(AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl);
|
||||||
|
return await SendTestRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> TestProviderProxyDelay(string name, string providerName)
|
||||||
|
{
|
||||||
|
var url =
|
||||||
|
$"{ApiUrl}/providers/proxies/{Utils.UrlEncode(providerName)}/{Utils.UrlEncode(name)}/healthcheck?timeout=10000&url=" +
|
||||||
|
Utils.UrlEncode(AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl);
|
||||||
|
return await SendTestRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> SendTestRequest(string url)
|
||||||
|
{
|
||||||
|
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||||
|
var jsonObject = JsonUtils.ParseJson(result) as JsonObject;
|
||||||
|
return jsonObject?["delay"] is { } n && n.GetValueKind() == JsonValueKind.Number &&
|
||||||
|
n.GetValue<JsonElement>().TryGetInt32(out var v)
|
||||||
|
? v
|
||||||
|
: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetActiveProxy(string groupName, string nodeName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fileContent = ProfileContent;
|
var url = $"{ApiUrl}/proxies/{Utils.UrlEncode(groupName)}";
|
||||||
if (fileContent is null || fileContent?.ContainsKey("proxy-groups") == false)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return JsonUtils.Deserialize<List<ProxiesItem>>(JsonUtils.Serialize(fileContent["proxy-groups"]));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ClashSetActiveProxy(string name, string nameNode)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var url = $"{GetApiUrl()}/proxies/{name}";
|
|
||||||
var headers = new Dictionary<string, string>();
|
var headers = new Dictionary<string, string>();
|
||||||
headers.Add("name", nameNode);
|
headers.Add("name", nodeName);
|
||||||
await HttpClientHelper.Instance.PutAsync(url, headers);
|
await HttpClientHelper.Instance.PutAsync(url, headers);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -116,39 +107,67 @@ public sealed class ClashApiManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ClashConfigUpdate(Dictionary<string, string> headers)
|
public async Task UpdateClashMode(string mode)
|
||||||
{
|
{
|
||||||
if (_proxies == null)
|
var headers = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
return;
|
{ "mode", mode },
|
||||||
}
|
};
|
||||||
|
await UpdateConfig(headers);
|
||||||
|
}
|
||||||
|
|
||||||
var urlBase = $"{GetApiUrl()}/configs";
|
public async Task UpdateConfig(Dictionary<string, string> headers)
|
||||||
|
{
|
||||||
|
var urlBase = $"{ApiUrl}/configs";
|
||||||
|
|
||||||
await HttpClientHelper.Instance.PatchAsync(urlBase, headers);
|
await HttpClientHelper.Instance.PatchAsync(urlBase, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ClashConfigReload(string filePath)
|
public async Task<List<string>> GetClashModes()
|
||||||
{
|
{
|
||||||
await ClashConnectionClose("");
|
var jsonNode = await GetConfig();
|
||||||
try
|
if ((jsonNode?["mode-list"] ?? jsonNode?["modes"]) is not JsonArray { Count: > 0 } modesArray)
|
||||||
{
|
{
|
||||||
var url = $"{GetApiUrl()}/configs?force=true";
|
return [];
|
||||||
var headers = new Dictionary<string, string>();
|
|
||||||
headers.Add("path", filePath);
|
|
||||||
await HttpClientHelper.Instance.PutAsync(url, headers);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
var modes = new List<string>();
|
||||||
|
foreach (var mode in modesArray)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
if (mode is JsonValue jsonValue && jsonValue.GetValueKind() == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
modes.Add(jsonValue.GetValue<string>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return modes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ClashConnections?> GetClashConnectionsAsync()
|
public async Task<string?> GetClashMode()
|
||||||
|
{
|
||||||
|
var jsonNode = await GetConfig();
|
||||||
|
if (jsonNode["mode"] is not JsonValue jsonValue || jsonValue.GetValueKind() != JsonValueKind.String)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return jsonValue.GetValue<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<JsonObject> GetConfig()
|
||||||
|
{
|
||||||
|
var url = $"{ApiUrl}/configs";
|
||||||
|
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||||
|
var jsonNode = JsonUtils.ParseJson(result);
|
||||||
|
if (jsonNode is not JsonObject jsonObject)
|
||||||
|
{
|
||||||
|
return new JsonObject();
|
||||||
|
}
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ClashConnections?> GetConnections()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var url = $"{GetApiUrl()}/connections";
|
var url = $"{ApiUrl}/connections";
|
||||||
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
var result = await HttpClientHelper.Instance.TryGetAsync(url);
|
||||||
var clashConnections = JsonUtils.Deserialize<ClashConnections>(result);
|
var clashConnections = JsonUtils.Deserialize<ClashConnections>(result);
|
||||||
|
|
||||||
@@ -162,11 +181,11 @@ public sealed class ClashApiManager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ClashConnectionClose(string id)
|
public async Task CloseConnection(string id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var url = $"{GetApiUrl()}/connections/{id}";
|
var url = $"{ApiUrl}/connections/{id}";
|
||||||
await HttpClientHelper.Instance.DeleteAsync(url);
|
await HttpClientHelper.Instance.DeleteAsync(url);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -174,9 +193,4 @@ public sealed class ClashApiManager
|
|||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetApiUrl()
|
|
||||||
{
|
|
||||||
return $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort2}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,7 +209,6 @@ public class HysteriaItem
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class ClashUIItem
|
public class ClashUIItem
|
||||||
{
|
{
|
||||||
public ERuleMode RuleMode { get; set; }
|
|
||||||
public bool EnableIPv6 { get; set; }
|
public bool EnableIPv6 { get; set; }
|
||||||
public bool EnableMixinContent { get; set; }
|
public bool EnableMixinContent { get; set; }
|
||||||
public int ProxiesSorting { get; set; }
|
public int ProxiesSorting { get; set; }
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
namespace ServiceLib.Models.Dto;
|
||||||
|
|
||||||
|
public record ClashItem
|
||||||
|
{
|
||||||
|
public Dictionary<string, ClashProxy> Proxies { get; init; } = [];
|
||||||
|
public Dictionary<string, string> ProviderIndexMap { get; init; } = [];
|
||||||
|
|
||||||
|
public bool IsEmpty() => Proxies.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ClashProxy
|
||||||
|
{
|
||||||
|
public List<string>? all { get; init; }
|
||||||
|
public List<HistoryItem>? history { get; init; }
|
||||||
|
public string? name { get; init; }
|
||||||
|
public string? type { get; init; }
|
||||||
|
public bool udp { get; init; }
|
||||||
|
public string? now { get; init; }
|
||||||
|
public int delay { get; init; }
|
||||||
|
|
||||||
|
public record HistoryItem
|
||||||
|
{
|
||||||
|
public string? time { get; init; }
|
||||||
|
public int delay { get; init; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ClashProvider
|
||||||
|
{
|
||||||
|
public string? name { get; init; }
|
||||||
|
public List<ClashProxy>? proxies { get; init; }
|
||||||
|
public string? type { get; init; }
|
||||||
|
public string? vehicleType { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ClashProxies
|
||||||
|
{
|
||||||
|
public Dictionary<string, ClashProxy> proxies { get; init; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ClashProviders
|
||||||
|
{
|
||||||
|
public Dictionary<string, ClashProvider> providers { get; init; } = [];
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using static ServiceLib.Models.Dto.ClashProxies;
|
|
||||||
|
|
||||||
namespace ServiceLib.Models.Dto;
|
|
||||||
|
|
||||||
public class ClashProviders
|
|
||||||
{
|
|
||||||
public Dictionary<string, ProvidersItem>? providers { get; set; }
|
|
||||||
|
|
||||||
public class ProvidersItem
|
|
||||||
{
|
|
||||||
public string? name { get; set; }
|
|
||||||
public List<ProxiesItem>? proxies { get; set; }
|
|
||||||
public string? type { get; set; }
|
|
||||||
public string? vehicleType { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
namespace ServiceLib.Models.Dto;
|
|
||||||
|
|
||||||
public class ClashProxies
|
|
||||||
{
|
|
||||||
public Dictionary<string, ProxiesItem>? proxies { get; set; }
|
|
||||||
|
|
||||||
public class ProxiesItem
|
|
||||||
{
|
|
||||||
public List<string>? all { get; set; }
|
|
||||||
public List<HistoryItem>? history { get; set; }
|
|
||||||
public string? name { get; set; }
|
|
||||||
public string? type { get; set; }
|
|
||||||
public bool udp { get; set; }
|
|
||||||
public string? now { get; set; }
|
|
||||||
public int delay { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HistoryItem
|
|
||||||
{
|
|
||||||
public string? time { get; set; }
|
|
||||||
public int delay { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,9 @@ namespace ServiceLib.Models.Dto;
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public partial class ClashProxyModel : ReactiveObject
|
public partial class ClashProxyModel : ReactiveObject
|
||||||
{
|
{
|
||||||
public string? Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
||||||
public string? Type { get; set; }
|
public required string Type { get; set; }
|
||||||
|
|
||||||
public string? Now { get; set; }
|
public string? Now { get; set; }
|
||||||
|
|
||||||
|
|||||||
Generated
+9
-9
@@ -987,6 +987,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Latency Test 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string menuDelaytest {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("menuDelaytest", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 DNS Settings 的本地化字符串。
|
/// 查找类似 DNS Settings 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1401,15 +1410,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 Latency Test 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string menuProxiesDelaytest {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("menuProxiesDelaytest", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Part Node Latency Test 的本地化字符串。
|
/// 查找类似 Part Node Latency Test 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1215,7 +1215,7 @@
|
|||||||
<data name="menuModeRule" xml:space="preserve">
|
<data name="menuModeRule" xml:space="preserve">
|
||||||
<value>قانون</value>
|
<value>قانون</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
<data name="menuDelaytest" xml:space="preserve">
|
||||||
<value>تست تأخیر</value>
|
<value>تست تأخیر</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||||
|
|||||||
@@ -1212,7 +1212,7 @@
|
|||||||
<data name="menuModeRule" xml:space="preserve">
|
<data name="menuModeRule" xml:space="preserve">
|
||||||
<value>Règle</value>
|
<value>Règle</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
<data name="menuDelaytest" xml:space="preserve">
|
||||||
<value>Test de latence</value>
|
<value>Test de latence</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||||
|
|||||||
@@ -1215,7 +1215,7 @@
|
|||||||
<data name="menuModeRule" xml:space="preserve">
|
<data name="menuModeRule" xml:space="preserve">
|
||||||
<value>Szabály</value>
|
<value>Szabály</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
<data name="menuDelaytest" xml:space="preserve">
|
||||||
<value>Késleltetés teszt</value>
|
<value>Késleltetés teszt</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||||
|
|||||||
@@ -1215,7 +1215,7 @@
|
|||||||
<data name="menuModeRule" xml:space="preserve">
|
<data name="menuModeRule" xml:space="preserve">
|
||||||
<value>Aturan</value>
|
<value>Aturan</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
<data name="menuDelaytest" xml:space="preserve">
|
||||||
<value>Uji latensi</value>
|
<value>Uji latensi</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||||
|
|||||||
@@ -1221,7 +1221,7 @@
|
|||||||
<data name="menuModeRule" xml:space="preserve">
|
<data name="menuModeRule" xml:space="preserve">
|
||||||
<value>Rule</value>
|
<value>Rule</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
<data name="menuDelaytest" xml:space="preserve">
|
||||||
<value>Latency Test</value>
|
<value>Latency Test</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||||
|
|||||||
@@ -1221,7 +1221,7 @@
|
|||||||
<data name="menuModeRule" xml:space="preserve">
|
<data name="menuModeRule" xml:space="preserve">
|
||||||
<value>Правила</value>
|
<value>Правила</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
<data name="menuDelaytest" xml:space="preserve">
|
||||||
<value>Тест задержки</value>
|
<value>Тест задержки</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||||
|
|||||||
@@ -1218,7 +1218,7 @@
|
|||||||
<data name="menuModeRule" xml:space="preserve">
|
<data name="menuModeRule" xml:space="preserve">
|
||||||
<value>规则</value>
|
<value>规则</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
<data name="menuDelaytest" xml:space="preserve">
|
||||||
<value>延迟测试</value>
|
<value>延迟测试</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||||
|
|||||||
@@ -1218,7 +1218,7 @@
|
|||||||
<data name="menuModeRule" xml:space="preserve">
|
<data name="menuModeRule" xml:space="preserve">
|
||||||
<value>規則</value>
|
<value>規則</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytest" xml:space="preserve">
|
<data name="menuDelaytest" xml:space="preserve">
|
||||||
<value>延遲測試</value>
|
<value>延遲測試</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
<data name="menuProxiesDelaytestPart" xml:space="preserve">
|
||||||
|
|||||||
@@ -94,17 +94,7 @@ public class CoreConfigClashService(Config config, bool isTunEnabled)
|
|||||||
fileContent["ipv6"] = config.ClashUIItem.EnableIPv6;
|
fileContent["ipv6"] = config.ClashUIItem.EnableIPv6;
|
||||||
|
|
||||||
//mode
|
//mode
|
||||||
if (!fileContent.ContainsKey("mode"))
|
fileContent.TryAdd("mode", nameof(ERuleMode.Rule));
|
||||||
{
|
|
||||||
fileContent["mode"] = nameof(ERuleMode.Rule).ToLower();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (config.ClashUIItem.RuleMode != ERuleMode.Unchanged)
|
|
||||||
{
|
|
||||||
fileContent["mode"] = config.ClashUIItem.RuleMode.ToString().ToLower();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//enable tun mode
|
//enable tun mode
|
||||||
if (isTunEnabled)
|
if (isTunEnabled)
|
||||||
@@ -159,8 +149,6 @@ public class CoreConfigClashService(Config config, bool isTunEnabled)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClashApiManager.Instance.ProfileContent = fileContent;
|
|
||||||
|
|
||||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}");
|
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, $"{node.GetSummary()}");
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
@@ -2,33 +2,18 @@ namespace ServiceLib.ViewModels;
|
|||||||
|
|
||||||
public partial class ClashConnectionsViewModel : MyReactiveObject
|
public partial class ClashConnectionsViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
public BulkObservableCollection<ClashConnectionModel> ConnectionItems { get; } = [];
|
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public partial ClashConnectionModel SelectedSource { get; set; }
|
|
||||||
|
|
||||||
public ReactiveCommand<RxVoid, RxVoid> ConnectionCloseCmd { get; }
|
|
||||||
public ReactiveCommand<RxVoid, RxVoid> ConnectionCloseAllCmd { get; }
|
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public partial string HostFilter { get; set; }
|
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public partial bool AutoRefresh { get; set; }
|
|
||||||
|
|
||||||
public ClashConnectionsViewModel()
|
public ClashConnectionsViewModel()
|
||||||
{
|
{
|
||||||
_config = AppManager.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh;
|
AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh;
|
||||||
|
|
||||||
var canEditRemove = this.WhenAnyValue(
|
var canEditRemove = this.WhenAnyValue(
|
||||||
x => x.SelectedSource,
|
x => x.SelectedSource,
|
||||||
selectedSource => selectedSource != null && selectedSource.Id.IsNotEmpty());
|
selectedSource => selectedSource?.Id?.IsNotEmpty() == true);
|
||||||
|
|
||||||
this.WhenAnyValue(
|
this.WhenAnyValue(
|
||||||
x => x.AutoRefresh,
|
x => x.AutoRefresh)
|
||||||
y => y == true)
|
.Subscribe(_ => { _config.ClashUIItem.ConnectionsAutoRefresh = AutoRefresh; });
|
||||||
.Subscribe(c => { _config.ClashUIItem.ConnectionsAutoRefresh = AutoRefresh; });
|
|
||||||
ConnectionCloseCmd = ReactiveCommand.CreateFromTask(async () =>
|
ConnectionCloseCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
await ClashConnectionClose(false);
|
await ClashConnectionClose(false);
|
||||||
@@ -39,17 +24,28 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
await ClashConnectionClose(true);
|
await ClashConnectionClose(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
_ = Init();
|
_ = Task.Factory.StartNew(
|
||||||
|
async () => await GetClashConnectionsTask(),
|
||||||
|
CancellationToken.None,
|
||||||
|
TaskCreationOptions.LongRunning,
|
||||||
|
TaskScheduler.Default
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Init()
|
public BulkObservableCollection<ClashConnectionModel> ConnectionItems { get; } = [];
|
||||||
{
|
|
||||||
await DelayTestTask();
|
[Reactive] public partial ClashConnectionModel SelectedSource { get; set; }
|
||||||
}
|
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> ConnectionCloseCmd { get; }
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> ConnectionCloseAllCmd { get; }
|
||||||
|
|
||||||
|
[Reactive] public partial string HostFilter { get; set; }
|
||||||
|
|
||||||
|
[Reactive] public partial bool AutoRefresh { get; set; }
|
||||||
|
|
||||||
private async Task GetClashConnections()
|
private async Task GetClashConnections()
|
||||||
{
|
{
|
||||||
var ret = await ClashApiManager.Instance.GetClashConnectionsAsync();
|
var ret = await ClashApiManager.Instance.GetConnections();
|
||||||
if (ret == null)
|
if (ret == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -69,7 +65,8 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
var lstModel = new List<ClashConnectionModel>();
|
var lstModel = new List<ClashConnectionModel>();
|
||||||
foreach (var item in connections ?? [])
|
foreach (var item in connections ?? [])
|
||||||
{
|
{
|
||||||
var host = $"{(item.metadata.host.IsNullOrEmpty() ? item.metadata.destinationIP : item.metadata.host)}:{item.metadata.destinationPort}";
|
var host =
|
||||||
|
$"{(item.metadata.host.IsNullOrEmpty() ? item.metadata.destinationIP : item.metadata.host)}:{item.metadata.destinationPort}";
|
||||||
if (HostFilter.IsNotEmpty() && !host.Contains(HostFilter))
|
if (HostFilter.IsNotEmpty() && !host.Contains(HostFilter))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -83,7 +80,7 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
Host = host,
|
Host = host,
|
||||||
Time = (dtNow - item.start).TotalSeconds < 0 ? 1 : (dtNow - item.start).TotalSeconds,
|
Time = (dtNow - item.start).TotalSeconds < 0 ? 1 : (dtNow - item.start).TotalSeconds,
|
||||||
Elapsed = (dtNow - item.start).ToString(@"hh\:mm\:ss"),
|
Elapsed = (dtNow - item.start).ToString(@"hh\:mm\:ss"),
|
||||||
Chain = $"{item.rule} , {string.Join("->", item.chains ?? [])}"
|
Chain = $"{item.rule} , {string.Join("->", item.chains ?? [])}",
|
||||||
};
|
};
|
||||||
|
|
||||||
lstModel.Add(model);
|
lstModel.Add(model);
|
||||||
@@ -103,47 +100,39 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
|
|||||||
if (!all)
|
if (!all)
|
||||||
{
|
{
|
||||||
var item = SelectedSource;
|
var item = SelectedSource;
|
||||||
if (item is null)
|
if (string.IsNullOrEmpty(item?.Id))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
id = item.Id;
|
id = item.Id;
|
||||||
}
|
}
|
||||||
else
|
await ClashApiManager.Instance.CloseConnection(id);
|
||||||
{
|
|
||||||
ConnectionItems.Clear();
|
|
||||||
}
|
|
||||||
await ClashApiManager.Instance.ClashConnectionClose(id);
|
|
||||||
await GetClashConnections();
|
await GetClashConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DelayTestTask()
|
public async Task GetClashConnectionsTask()
|
||||||
{
|
{
|
||||||
_ = Task.Run(async () =>
|
var numOfExecuted = 1;
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
var numOfExecuted = 1;
|
await Task.Delay(1000 * 5);
|
||||||
while (true)
|
numOfExecuted++;
|
||||||
|
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
|
||||||
|
AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||||
{
|
{
|
||||||
await Task.Delay(1000 * 5);
|
continue;
|
||||||
numOfExecuted++;
|
|
||||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_config.ClashUIItem.ConnectionsRefreshInterval <= 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numOfExecuted % _config.ClashUIItem.ConnectionsRefreshInterval != 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
await GetClashConnections();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
await Task.CompletedTask;
|
if (_config.ClashUIItem.ConnectionsRefreshInterval <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numOfExecuted % _config.ClashUIItem.ConnectionsRefreshInterval != 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await GetClashConnections();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,9 @@
|
|||||||
using static ServiceLib.Models.Dto.ClashProviders;
|
|
||||||
using static ServiceLib.Models.Dto.ClashProxies;
|
|
||||||
|
|
||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public partial class ClashProxiesViewModel : MyReactiveObject
|
public partial class ClashProxiesViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private Dictionary<string, ProxiesItem>? _proxies;
|
|
||||||
private Dictionary<string, ProvidersItem>? _providers;
|
|
||||||
private readonly int _delayTimeout = 99999999;
|
private readonly int _delayTimeout = 99999999;
|
||||||
|
private ClashItem _clashItem = new();
|
||||||
public BulkObservableCollection<ClashProxyModel> ProxyGroups { get; } = [];
|
|
||||||
public BulkObservableCollection<ClashProxyModel> ProxyDetails { get; } = [];
|
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public partial ClashProxyModel SelectedGroup { get; set; }
|
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public partial ClashProxyModel SelectedDetail { get; set; }
|
|
||||||
|
|
||||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesReloadCmd { get; }
|
|
||||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesDelayTestCmd { get; }
|
|
||||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesDelayTestPartCmd { get; }
|
|
||||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesSelectActivityCmd { get; }
|
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public partial int RuleModeSelected { get; set; }
|
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public partial int SortingSelected { get; set; }
|
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public partial bool AutoRefresh { get; set; }
|
|
||||||
|
|
||||||
public ClashProxiesViewModel()
|
public ClashProxiesViewModel()
|
||||||
{
|
{
|
||||||
@@ -40,173 +13,158 @@ public partial class ClashProxiesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
await ProxiesReload();
|
await ProxiesReload();
|
||||||
});
|
});
|
||||||
ProxiesDelayTestCmd = ReactiveCommand.CreateFromTask(async () =>
|
ProxyDelayTestCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
await ProxiesDelayTest(true);
|
if (!string.IsNullOrEmpty(SelectedDetail?.Name))
|
||||||
|
{
|
||||||
|
await TestProxyDelay(SelectedDetail.Name);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ProxiesDelayTestPartCmd = ReactiveCommand.CreateFromTask(async () =>
|
GroupProxiesDelayTestCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
await ProxiesDelayTest(false);
|
await TestGroupProxiesDelay();
|
||||||
});
|
});
|
||||||
ProxiesSelectActivityCmd = ReactiveCommand.CreateFromTask(async () =>
|
ProxiesSelectActivityCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
await SetActiveProxy();
|
await SetActiveProxy();
|
||||||
});
|
});
|
||||||
|
|
||||||
SelectedGroup = new();
|
|
||||||
SelectedDetail = new();
|
|
||||||
AutoRefresh = _config.ClashUIItem.ProxiesAutoRefresh;
|
AutoRefresh = _config.ClashUIItem.ProxiesAutoRefresh;
|
||||||
SortingSelected = _config.ClashUIItem.ProxiesSorting;
|
SortingSelected = _config.ClashUIItem.ProxiesSorting;
|
||||||
RuleModeSelected = (int)_config.ClashUIItem.RuleMode;
|
RuleModeSelected = nameof(ERuleMode.Rule);
|
||||||
|
|
||||||
#region WhenAnyValue && ReactiveCommand
|
#region WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
this.WhenAnyValue(
|
this.WhenAnyValue(x => x.SelectedGroup)
|
||||||
x => x.SelectedGroup,
|
.Where(y => y != null && y.Name.IsNotEmpty())
|
||||||
y => y != null && y.Name.IsNotEmpty())
|
.Subscribe(_ => RefreshProxyDetails());
|
||||||
.Subscribe(RefreshProxyDetails);
|
|
||||||
|
|
||||||
this.WhenAnyValue(
|
this.WhenAnyValue(x => x.RuleModeSelected)
|
||||||
x => x.RuleModeSelected,
|
.Where(y => !string.IsNullOrEmpty(y))
|
||||||
y => y >= 0)
|
.Skip(1)
|
||||||
.Subscribe(async c => await DoRuleModeSelected(c));
|
.SubscribeAsync(async x => await SetRuleMode(x));
|
||||||
|
|
||||||
this.WhenAnyValue(
|
this.WhenAnyValue(x => x.SortingSelected)
|
||||||
x => x.SortingSelected,
|
.Where(y => y >= 0)
|
||||||
y => y >= 0)
|
.Subscribe(_ => DoSortingSelected());
|
||||||
.Subscribe(DoSortingSelected);
|
|
||||||
|
|
||||||
this.WhenAnyValue(
|
this.WhenAnyValue(x => x.AutoRefresh)
|
||||||
x => x.AutoRefresh,
|
.Where(y => y)
|
||||||
y => y == true)
|
.Subscribe(_ => { _config.ClashUIItem.ProxiesAutoRefresh = AutoRefresh; });
|
||||||
.Subscribe(c => { _config.ClashUIItem.ProxiesAutoRefresh = AutoRefresh; });
|
|
||||||
|
|
||||||
#endregion WhenAnyValue && ReactiveCommand
|
#endregion WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
_ = Init();
|
_ = Task.Factory.StartNew(
|
||||||
|
async () => await GetClashProxiesTask(),
|
||||||
|
CancellationToken.None,
|
||||||
|
TaskCreationOptions.LongRunning,
|
||||||
|
TaskScheduler.Default
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Init()
|
public BulkObservableCollection<ClashProxyModel> ProxyGroups { get; } = [];
|
||||||
{
|
public BulkObservableCollection<ClashProxyModel> ProxyDetails { get; } = [];
|
||||||
await DelayTestTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DoRuleModeSelected(bool c)
|
public BulkObservableCollection<string> ClashModes { get; } = new(Enum.GetNames<ERuleMode>().ToList());
|
||||||
{
|
|
||||||
if (!c)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_config.ClashUIItem.RuleMode == (ERuleMode)RuleModeSelected)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await SetRuleModeCheck((ERuleMode)RuleModeSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SetRuleModeCheck(ERuleMode mode)
|
[Reactive] public partial ClashProxyModel? SelectedGroup { get; set; }
|
||||||
{
|
|
||||||
if (_config.ClashUIItem.RuleMode == mode)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await SetRuleMode(mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DoSortingSelected(bool c)
|
[Reactive] public partial ClashProxyModel? SelectedDetail { get; set; }
|
||||||
|
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> ProxiesReloadCmd { get; }
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> ProxyDelayTestCmd { get; }
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> GroupProxiesDelayTestCmd { get; }
|
||||||
|
public ReactiveCommand<RxVoid, RxVoid> ProxiesSelectActivityCmd { get; }
|
||||||
|
|
||||||
|
[Reactive] public partial string RuleModeSelected { get; set; }
|
||||||
|
|
||||||
|
[Reactive] public partial int SortingSelected { get; set; }
|
||||||
|
|
||||||
|
[Reactive] public partial bool AutoRefresh { get; set; }
|
||||||
|
|
||||||
|
private void DoSortingSelected()
|
||||||
{
|
{
|
||||||
if (!c)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SortingSelected != _config.ClashUIItem.ProxiesSorting)
|
if (SortingSelected != _config.ClashUIItem.ProxiesSorting)
|
||||||
{
|
{
|
||||||
_config.ClashUIItem.ProxiesSorting = SortingSelected;
|
_config.ClashUIItem.ProxiesSorting = SortingSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
RefreshProxyDetails(c);
|
RefreshProxyDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ProxiesReload()
|
public async Task ProxiesReload()
|
||||||
{
|
{
|
||||||
await GetClashProxies(true);
|
await GetClashProxies();
|
||||||
await ProxiesDelayTest();
|
await GetClashModes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region task
|
||||||
|
|
||||||
|
public async Task GetClashProxiesTask()
|
||||||
|
{
|
||||||
|
var numOfExecuted = 1;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await Task.Delay(1000 * 60);
|
||||||
|
numOfExecuted++;
|
||||||
|
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
|
||||||
|
AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (_config.ClashUIItem.ProxiesAutoDelayTestInterval <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (numOfExecuted % _config.ClashUIItem.ProxiesAutoDelayTestInterval != 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await GetClashProxies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion task
|
||||||
|
|
||||||
#region proxy function
|
#region proxy function
|
||||||
|
|
||||||
private async Task SetRuleMode(ERuleMode mode)
|
private async Task SetRuleMode(string mode)
|
||||||
{
|
{
|
||||||
_config.ClashUIItem.RuleMode = mode;
|
await ClashApiManager.Instance.UpdateClashMode(mode);
|
||||||
|
|
||||||
if (mode != ERuleMode.Unchanged)
|
|
||||||
{
|
|
||||||
Dictionary<string, string> headers = new()
|
|
||||||
{
|
|
||||||
{ "mode", mode.ToString().ToLower() }
|
|
||||||
};
|
|
||||||
await ClashApiManager.Instance.ClashConfigUpdate(headers);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetClashProxies(bool refreshUI)
|
private async Task GetClashProxies()
|
||||||
{
|
{
|
||||||
var ret = await ClashApiManager.Instance.GetClashProxiesAsync();
|
var ret = await ClashApiManager.Instance.GetProxies();
|
||||||
if (ret?.Item1 == null || ret.Item2 == null)
|
if (ret?.IsEmpty() != false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_proxies = ret.Item1.proxies;
|
_clashItem = ret;
|
||||||
_providers = ret?.Item2.providers;
|
|
||||||
|
|
||||||
if (refreshUI)
|
RxSchedulers.MainThreadScheduler.Schedule(() => _ = RefreshProxyGroups());
|
||||||
{
|
|
||||||
RxSchedulers.MainThreadScheduler.Schedule(() => _ = RefreshProxyGroups());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshProxyGroups()
|
public async Task RefreshProxyGroups()
|
||||||
{
|
{
|
||||||
if (_proxies == null)
|
if (_clashItem.IsEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedName = SelectedGroup?.Name;
|
var selectedName = SelectedGroup?.Name;
|
||||||
ProxyGroups.Clear();
|
|
||||||
|
|
||||||
var lstProxyGroups = new List<ClashProxyModel>();
|
var lstProxyGroups = new List<ClashProxyModel>();
|
||||||
var proxyGroups = ClashApiManager.Instance.GetClashProxyGroups();
|
|
||||||
if (proxyGroups is { Count: > 0 })
|
|
||||||
{
|
|
||||||
foreach (var it in proxyGroups)
|
|
||||||
{
|
|
||||||
if (it.name.IsNullOrEmpty() || !_proxies.TryGetValue(it.name, out var item))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!Global.allowSelectType.Contains(item.type.ToLower()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
lstProxyGroups.Add(new ClashProxyModel()
|
|
||||||
{
|
|
||||||
Now = item.now,
|
|
||||||
Name = item.name,
|
|
||||||
Type = item.type
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//from api
|
var globalName = "GLOBAL";
|
||||||
foreach (var kv in _proxies)
|
foreach (var kv in _clashItem.Proxies)
|
||||||
{
|
{
|
||||||
if (!Global.allowSelectType.Contains(kv.Value.type?.ToLower()))
|
if (!Global.allowSelectType.Contains(kv.Value.type?.ToLower()))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (kv.Key == "GLOBAL")
|
if (kv.Key == globalName)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -215,61 +173,49 @@ public partial class ClashProxiesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
lstProxyGroups.Add(new ClashProxyModel()
|
lstProxyGroups.Add(new ClashProxyModel
|
||||||
{
|
{
|
||||||
Now = kv.Value.now,
|
Now = kv.Value.now,
|
||||||
Name = kv.Key,
|
Name = kv.Key,
|
||||||
Type = kv.Value.type,
|
Type = kv.Value.type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (_proxies.TryGetValue("GLOBAL", out var globalProxy))
|
if (_clashItem.Proxies.TryGetValue(globalName, out var globalProxy))
|
||||||
{
|
{
|
||||||
lstProxyGroups.Add(new ClashProxyModel()
|
lstProxyGroups.Add(new ClashProxyModel
|
||||||
{
|
{
|
||||||
Now = globalProxy.now,
|
Now = globalProxy.now,
|
||||||
Name = "GLOBAL",
|
Name = globalName,
|
||||||
Type = globalProxy.type,
|
Type = globalProxy.type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ProxyGroups.AddRange(lstProxyGroups);
|
ProxyGroups.ReplaceRange(lstProxyGroups);
|
||||||
|
|
||||||
if (ProxyGroups is { Count: > 0 })
|
if (ProxyGroups is { Count: > 0 })
|
||||||
{
|
{
|
||||||
if (selectedName != null && ProxyGroups.Any(t => t.Name == selectedName))
|
SelectedGroup = ProxyGroups.FirstOrDefault(t => t.Name == selectedName) ?? ProxyGroups.First();
|
||||||
{
|
|
||||||
SelectedGroup = ProxyGroups.FirstOrDefault(t => t.Name == selectedName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SelectedGroup = ProxyGroups.First();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SelectedGroup = new();
|
SelectedGroup = null;
|
||||||
}
|
}
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshProxyDetails(bool c)
|
private void RefreshProxyDetails()
|
||||||
{
|
{
|
||||||
ProxyDetails.Clear();
|
|
||||||
if (!c)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var name = SelectedGroup?.Name;
|
var name = SelectedGroup?.Name;
|
||||||
if (name.IsNullOrEmpty())
|
if (name.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_proxies == null)
|
if (_clashItem.IsEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_proxies.TryGetValue(name, out var proxy);
|
_clashItem.Proxies.TryGetValue(name, out var proxy);
|
||||||
if (proxy?.all == null)
|
if (proxy?.all == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -284,7 +230,7 @@ public partial class ClashProxiesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
var delay = proxy2.history?.Count > 0 ? proxy2.history.Last().delay : -1;
|
var delay = proxy2.history?.Count > 0 ? proxy2.history.Last().delay : -1;
|
||||||
|
|
||||||
lstDetails.Add(new ClashProxyModel()
|
lstDetails.Add(new ClashProxyModel
|
||||||
{
|
{
|
||||||
IsActive = item == proxy.now,
|
IsActive = item == proxy.now,
|
||||||
Name = item,
|
Name = item,
|
||||||
@@ -293,7 +239,7 @@ public partial class ClashProxiesViewModel : MyReactiveObject
|
|||||||
DelayName = delay <= 0 ? string.Empty : $"{delay}ms",
|
DelayName = delay <= 0 ? string.Empty : $"{delay}ms",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//sort
|
// sort
|
||||||
switch (SortingSelected)
|
switch (SortingSelected)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
@@ -303,101 +249,95 @@ public partial class ClashProxiesViewModel : MyReactiveObject
|
|||||||
case 1:
|
case 1:
|
||||||
lstDetails = lstDetails.OrderBy(t => t.Name).ToList();
|
lstDetails = lstDetails.OrderBy(t => t.Name).ToList();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
ProxyDetails.AddRange(lstDetails);
|
ProxyDetails.ReplaceRange(lstDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProxiesItem? TryGetProxy(string name)
|
private ClashProxy? TryGetProxy(string? name)
|
||||||
{
|
{
|
||||||
if (_proxies == null)
|
if (name.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
_proxies.TryGetValue(name, out var proxy2);
|
_clashItem.Proxies.TryGetValue(name, out var proxy2);
|
||||||
if (proxy2 != null)
|
return proxy2;
|
||||||
{
|
|
||||||
return proxy2;
|
|
||||||
}
|
|
||||||
//from providers
|
|
||||||
if (_providers != null)
|
|
||||||
{
|
|
||||||
foreach (var kv in _providers)
|
|
||||||
{
|
|
||||||
if (Global.proxyVehicleType.Contains(kv.Value.vehicleType.ToLower()))
|
|
||||||
{
|
|
||||||
var proxy3 = kv.Value.proxies.FirstOrDefault(t => t.name == name);
|
|
||||||
if (proxy3 != null)
|
|
||||||
{
|
|
||||||
return proxy3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetActiveProxy()
|
public async Task SetActiveProxy()
|
||||||
{
|
{
|
||||||
if (SelectedGroup == null || SelectedGroup.Name.IsNullOrEmpty())
|
if (SelectedGroup.Name.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (SelectedDetail == null || SelectedDetail.Name.IsNullOrEmpty())
|
if (SelectedDetail.Name.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var name = SelectedGroup.Name;
|
var groupName = SelectedGroup.Name;
|
||||||
if (name.IsNullOrEmpty())
|
if (groupName.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var nameNode = SelectedDetail.Name;
|
var nodeName = SelectedDetail.Name;
|
||||||
if (nameNode.IsNullOrEmpty())
|
if (nodeName.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var selectedProxy = TryGetProxy(name);
|
var selectedProxy = TryGetProxy(groupName);
|
||||||
if (selectedProxy == null || selectedProxy.type != "Selector")
|
if (selectedProxy is not { type: "Selector" })
|
||||||
{
|
{
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ClashApiManager.Instance.ClashSetActiveProxy(name, nameNode);
|
await ClashApiManager.Instance.SetActiveProxy(groupName, nodeName);
|
||||||
|
await GetClashProxies();
|
||||||
selectedProxy.now = nameNode;
|
|
||||||
var group = ProxyGroups.FirstOrDefault(it => it.Name == SelectedGroup.Name);
|
|
||||||
if (group != null)
|
|
||||||
{
|
|
||||||
group.Now = nameNode;
|
|
||||||
var group2 = JsonUtils.DeepCopy(group);
|
|
||||||
ProxyGroups.Replace(group, group2);
|
|
||||||
|
|
||||||
SelectedGroup = group2;
|
|
||||||
}
|
|
||||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProxiesDelayTest(bool blAll = true)
|
private async Task GetClashModes()
|
||||||
{
|
{
|
||||||
ClashApiManager.Instance.ClashProxiesDelayTest(blAll, ProxyDetails.ToList(), async (item, result) =>
|
var ret = await ClashApiManager.Instance.GetClashModes();
|
||||||
|
if (ret is not { Count: > 0 })
|
||||||
{
|
{
|
||||||
if (item == null || result.IsNullOrEmpty())
|
return;
|
||||||
{
|
}
|
||||||
return;
|
ClashModes.ReplaceRange(ret);
|
||||||
}
|
var currentMode = await ClashApiManager.Instance.GetClashMode();
|
||||||
|
if (currentMode.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RuleModeSelected = currentMode;
|
||||||
|
}
|
||||||
|
|
||||||
var model = new SpeedTestResult() { IndexId = item.Name, Delay = result };
|
private async Task TestProxyDelay(string name)
|
||||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
{
|
||||||
{
|
var result = await ClashApiManager.Instance.TestDelay(name, _clashItem);
|
||||||
_ = ProxiesDelayTestResult(model);
|
var model = new SpeedTestResult
|
||||||
});
|
{
|
||||||
await Task.CompletedTask;
|
IndexId = name,
|
||||||
|
Delay = result.ToString(),
|
||||||
|
};
|
||||||
|
await ProxiesDelayTestResult(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task TestGroupProxiesDelay()
|
||||||
|
{
|
||||||
|
var groupProxy = TryGetProxy(SelectedGroup.Name);
|
||||||
|
if (!Global.allowSelectType.Contains(groupProxy?.type))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new ParallelOptions
|
||||||
|
{
|
||||||
|
MaxDegreeOfParallelism = 4,
|
||||||
|
};
|
||||||
|
await Parallel.ForEachAsync(groupProxy?.all ?? [], options, async (name, _) =>
|
||||||
|
{
|
||||||
|
await TestProxyDelay(name);
|
||||||
});
|
});
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ProxiesDelayTestResult(SpeedTestResult result)
|
public async Task ProxiesDelayTestResult(SpeedTestResult result)
|
||||||
@@ -407,56 +347,10 @@ public partial class ClashProxiesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
detail.Delay = Convert.ToInt32(result.Delay);
|
||||||
var dicResult = JsonUtils.Deserialize<Dictionary<string, object>>(result.Delay);
|
detail.DelayName = $"{detail.Delay}ms";
|
||||||
if (dicResult != null && dicResult.TryGetValue("delay", out var value))
|
|
||||||
{
|
|
||||||
detail.Delay = Convert.ToInt32(value.ToString());
|
|
||||||
detail.DelayName = $"{detail.Delay}ms";
|
|
||||||
}
|
|
||||||
else if (dicResult != null && dicResult.TryGetValue("message", out var value1))
|
|
||||||
{
|
|
||||||
detail.Delay = _delayTimeout;
|
|
||||||
detail.DelayName = $"{value1}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
detail.Delay = _delayTimeout;
|
|
||||||
detail.DelayName = string.Empty;
|
|
||||||
}
|
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion proxy function
|
#endregion proxy function
|
||||||
|
|
||||||
#region task
|
|
||||||
|
|
||||||
public async Task DelayTestTask()
|
|
||||||
{
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var numOfExecuted = 1;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
await Task.Delay(1000 * 60);
|
|
||||||
numOfExecuted++;
|
|
||||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (_config.ClashUIItem.ProxiesAutoDelayTestInterval <= 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (numOfExecuted % _config.ClashUIItem.ProxiesAutoDelayTestInterval != 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
await ProxiesDelayTest();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion task
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,11 @@
|
|||||||
x:Name="cmbRulemode"
|
x:Name="cmbRulemode"
|
||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLr8}">
|
Margin="{StaticResource MarginLr8}">
|
||||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeRule}" />
|
<ComboBox.ItemTemplate>
|
||||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeGlobal}" />
|
<DataTemplate x:DataType="x:String">
|
||||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeDirect}" />
|
<TextBlock Text="{Binding}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -61,10 +63,10 @@
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
x:Name="menuProxiesDelaytest"
|
x:Name="menuGroupProxiesDelaytest"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
Classes="IconButton Success"
|
Classes="IconButton Success"
|
||||||
ToolTip.Tip="{x:Static resx:ResUI.menuProxiesDelaytest}">
|
ToolTip.Tip="{x:Static resx:ResUI.menuDelaytest}">
|
||||||
<Button.Content>
|
<Button.Content>
|
||||||
<PathIcon Data="{StaticResource SemiIconBolt}" />
|
<PathIcon Data="{StaticResource SemiIconBolt}" />
|
||||||
</Button.Content>
|
</Button.Content>
|
||||||
@@ -111,7 +113,7 @@
|
|||||||
<ListBox x:Name="lstProxyDetails" ItemsSource="{Binding ProxyDetails}">
|
<ListBox x:Name="lstProxyDetails" ItemsSource="{Binding ProxyDetails}">
|
||||||
<ItemsControl.ContextMenu>
|
<ItemsControl.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem x:Name="menuProxiesDelaytestPart" Header="{x:Static resx:ResUI.menuProxiesDelaytestPart}" />
|
<MenuItem x:Name="menuProxyDelaytest" Header="{x:Static resx:ResUI.menuDelaytest}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
x:Name="menuProxiesSelectActivity"
|
x:Name="menuProxiesSelectActivity"
|
||||||
Header="{x:Static resx:ResUI.menuProxiesSelectActivity}"
|
Header="{x:Static resx:ResUI.menuProxiesSelectActivity}"
|
||||||
|
|||||||
@@ -17,12 +17,13 @@ public partial class ClashProxiesView : ReactiveUserControl<ClashProxiesViewMode
|
|||||||
this.Bind(ViewModel, vm => vm.SelectedDetail, v => v.lstProxyDetails.SelectedItem).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedDetail, v => v.lstProxyDetails.SelectedItem).DisposeWith(disposables);
|
||||||
|
|
||||||
this.BindCommand(ViewModel, vm => vm.ProxiesReloadCmd, v => v.menuProxiesReload).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ProxiesReloadCmd, v => v.menuProxiesReload).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.ProxiesDelayTestCmd, v => v.menuProxiesDelaytest).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.GroupProxiesDelayTestCmd, v => v.menuGroupProxiesDelaytest).DisposeWith(disposables);
|
||||||
|
|
||||||
this.BindCommand(ViewModel, vm => vm.ProxiesDelayTestPartCmd, v => v.menuProxiesDelaytestPart).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ProxyDelayTestCmd, v => v.menuProxyDelaytest).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.ProxiesSelectActivityCmd, v => v.menuProxiesSelectActivity).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ProxiesSelectActivityCmd, v => v.menuProxiesSelectActivity).DisposeWith(disposables);
|
||||||
|
|
||||||
this.Bind(ViewModel, vm => vm.RuleModeSelected, v => v.cmbRulemode.SelectedIndex).DisposeWith(disposables);
|
this.OneWayBind(ViewModel, vm => vm.ClashModes, v => v.cmbRulemode.ItemsSource).DisposeWith(disposables);
|
||||||
|
this.Bind(ViewModel, vm => vm.RuleModeSelected, v => v.cmbRulemode.SelectedItem).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,9 +37,11 @@
|
|||||||
Width="100"
|
Width="100"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Style="{StaticResource DefComboBox}">
|
Style="{StaticResource DefComboBox}">
|
||||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeRule}" />
|
<ComboBox.ItemTemplate>
|
||||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeGlobal}" />
|
<DataTemplate>
|
||||||
<ComboBoxItem Content="{x:Static resx:ResUI.menuModeDirect}" />
|
<TextBlock Text="{Binding}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -68,12 +70,12 @@
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
x:Name="menuProxiesDelaytest"
|
x:Name="menuGroupProxiesDelaytest"
|
||||||
Width="24"
|
Width="24"
|
||||||
Height="24"
|
Height="24"
|
||||||
Margin="{StaticResource MarginLeftRight8}"
|
Margin="{StaticResource MarginLeftRight8}"
|
||||||
Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}"
|
Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}"
|
||||||
ToolTip="{x:Static resx:ResUI.menuProxiesDelaytest}">
|
ToolTip="{x:Static resx:ResUI.menuDelaytest}">
|
||||||
<materialDesign:PackIcon VerticalAlignment="Center" Kind="LightningBolt" />
|
<materialDesign:PackIcon VerticalAlignment="Center" Kind="LightningBolt" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@@ -141,7 +143,7 @@
|
|||||||
Style="{StaticResource MaterialDesignListView}">
|
Style="{StaticResource MaterialDesignListView}">
|
||||||
<ListView.ContextMenu>
|
<ListView.ContextMenu>
|
||||||
<ContextMenu Style="{StaticResource DefContextMenu}">
|
<ContextMenu Style="{StaticResource DefContextMenu}">
|
||||||
<MenuItem x:Name="menuProxiesDelaytestPart" Header="{x:Static resx:ResUI.menuProxiesDelaytestPart}" />
|
<MenuItem x:Name="menuProxyDelaytest" Header="{x:Static resx:ResUI.menuDelaytest}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
x:Name="menuProxiesSelectActivity"
|
x:Name="menuProxiesSelectActivity"
|
||||||
Header="{x:Static resx:ResUI.menuProxiesSelectActivity}"
|
Header="{x:Static resx:ResUI.menuProxiesSelectActivity}"
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ public partial class ClashProxiesView
|
|||||||
this.Bind(ViewModel, vm => vm.SelectedDetail, v => v.lstProxyDetails.SelectedItem).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedDetail, v => v.lstProxyDetails.SelectedItem).DisposeWith(disposables);
|
||||||
|
|
||||||
this.BindCommand(ViewModel, vm => vm.ProxiesReloadCmd, v => v.menuProxiesReload).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ProxiesReloadCmd, v => v.menuProxiesReload).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.ProxiesDelayTestCmd, v => v.menuProxiesDelaytest).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.GroupProxiesDelayTestCmd, v => v.menuGroupProxiesDelaytest).DisposeWith(disposables);
|
||||||
|
|
||||||
this.BindCommand(ViewModel, vm => vm.ProxiesDelayTestPartCmd, v => v.menuProxiesDelaytestPart).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ProxyDelayTestCmd, v => v.menuProxyDelaytest).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.ProxiesSelectActivityCmd, v => v.menuProxiesSelectActivity).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ProxiesSelectActivityCmd, v => v.menuProxiesSelectActivity).DisposeWith(disposables);
|
||||||
|
|
||||||
this.Bind(ViewModel, vm => vm.RuleModeSelected, v => v.cmbRulemode.SelectedIndex).DisposeWith(disposables);
|
this.OneWayBind(ViewModel, vm => vm.ClashModes, v => v.cmbRulemode.ItemsSource).DisposeWith(disposables);
|
||||||
|
this.Bind(ViewModel, vm => vm.RuleModeSelected, v => v.cmbRulemode.SelectedItem).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user