mirror of
https://github.com/2dust/v2rayN.git
synced 2025-10-13 11:59:13 +00:00

* Multi Profile * VM and wpf * avalonia * Fix right click not working * Exclude specific profile types from selection * Rename * Add Policy Group support * Add generate policy group * Adjust UI * Add Proxy Chain support * Fix * Add fallback support * Add PolicyGroup include other Group support * Add group in traffic splitting support * Avoid duplicate tags * Refactor * Adjust chained proxy, actual outbound is at the top Based on actual network flow instead of data packets * Add helper function * Refactor * Add chain selection control to group outbounds * Avoid self-reference * Fix * Improves Tun2Socks address handling * Avoids circular dependency in profile groups Adds cycle detection to prevent infinite loops when evaluating profile groups. This ensures that profile group configurations don't result in stack overflow errors when groups reference each other, directly or indirectly. * Fix * Fix * Update ProfileGroupItem.cs * Refactor * Remove unnecessary checks --------- Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
225 lines
7.6 KiB
C#
225 lines
7.6 KiB
C#
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())
|
|
{
|
|
continue;
|
|
}
|
|
childIndexIds.Add(item.IndexId);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
}
|