Compare commits

..

1 commit

Author SHA1 Message Date
DHR60
d6c81c3a9c PreCheck 2025-09-07 19:03:20 +08:00
68 changed files with 567 additions and 2854 deletions

View file

@ -1,67 +1,14 @@
#!/bin/bash
set -euo pipefail
# Install deps
sudo apt update -y
sudo apt install -y libfuse2 wget file
# Get tools
wget -qO appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool
# x86_64 AppDir
APPDIR_X64="AppDir-x86_64"
rm -rf "$APPDIR_X64"
mkdir -p "$APPDIR_X64/usr/lib/v2rayN" "$APPDIR_X64/usr/bin" "$APPDIR_X64/usr/share/applications" "$APPDIR_X64/usr/share/pixmaps"
cp -rf "$OutputPath64"/* "$APPDIR_X64/usr/lib/v2rayN" || true
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/usr/share/pixmaps/v2rayN.png" || true
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/v2rayN.png" || true
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_X64/AppRun"
chmod +x "$APPDIR_X64/AppRun"
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_X64/usr/bin/v2rayN"
cat > "$APPDIR_X64/v2rayN.desktop" <<EOF
[Desktop Entry]
Name=v2rayN
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
Exec=v2rayN
Icon=v2rayN
Terminal=false
Type=Application
Categories=Network;
EOF
install -Dm644 "$APPDIR_X64/v2rayN.desktop" "$APPDIR_X64/usr/share/applications/v2rayN.desktop"
ARCH=x86_64 ./appimagetool "$APPDIR_X64" "v2rayN-${OutputArch}.AppImage"
file "v2rayN-${OutputArch}.AppImage" | grep -q 'x86-64'
# aarch64 AppDir
APPDIR_ARM64="AppDir-aarch64"
rm -rf "$APPDIR_ARM64"
mkdir -p "$APPDIR_ARM64/usr/lib/v2rayN" "$APPDIR_ARM64/usr/bin" "$APPDIR_ARM64/usr/share/applications" "$APPDIR_ARM64/usr/share/pixmaps"
cp -rf "$OutputPathArm64"/* "$APPDIR_ARM64/usr/lib/v2rayN" || true
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/usr/share/pixmaps/v2rayN.png" || true
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/v2rayN.png" || true
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_ARM64/AppRun"
chmod +x "$APPDIR_ARM64/AppRun"
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_ARM64/usr/bin/v2rayN"
cat > "$APPDIR_ARM64/v2rayN.desktop" <<EOF
[Desktop Entry]
Name=v2rayN
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
Exec=v2rayN
Icon=v2rayN
Terminal=false
Type=Application
Categories=Network;
EOF
install -Dm644 "$APPDIR_ARM64/v2rayN.desktop" "$APPDIR_ARM64/usr/share/applications/v2rayN.desktop"
# aarch64 runtime
wget -qO runtime-aarch64 https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-aarch64
chmod +x runtime-aarch64
# build aarch64 AppImage
ARCH=aarch64 ./appimagetool --runtime-file ./runtime-aarch64 "$APPDIR_ARM64" "v2rayN-${OutputArchArm}.AppImage"
file "v2rayN-${OutputArchArm}.AppImage" | grep -q 'ARM aarch64'
sudo apt install -y libfuse2
wget -O pkg2appimage https://github.com/AppImageCommunity/pkg2appimage/releases/download/continuous/pkg2appimage-1eceb30-x86_64.AppImage
chmod a+x pkg2appimage
export AppImageOutputArch=$OutputArch
export OutputPath=$OutputPath64
./pkg2appimage ./pkg2appimage.yml
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage
export AppImageOutputArch=$OutputArchArm
export OutputPath=$OutputPathArm64
./pkg2appimage ./pkg2appimage.yml
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage

View file

@ -28,7 +28,7 @@ Package: v2rayN
Version: $Version
Architecture: $Arch2
Maintainer: https://github.com/2dust/v2rayN
Depends: desktop-file-utils, xdg-utils
Depends: desktop-file-utils
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
EOF

View file

@ -1,11 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
# ===== Require Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ====
if [[ -r /etc/os-release ]]; then
. /etc/os-release
case "$ID" in
rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
rhel|rocky|almalinux|centos|ubuntu|debian)
echo "[OK] Detected supported system: $NAME $VERSION_ID"
;;
*)
@ -390,30 +390,25 @@ download_mihomo() {
chmod +x "$outroot/bin/mihomo/mihomo" || true
}
# Move geo files to a unified path: outroot/bin
# Move geo files to a unified path: outroot/bin/xray/
unify_geo_layout() {
local outroot="$1"
mkdir -p "$outroot/bin"
local names=( \
"geosite.dat" \
"geoip.dat" \
"geoip-only-cn-private.dat" \
"Country.mmdb" \
"geoip.metadb" \
mkdir -p "$outroot/bin/xray"
local srcs=( \
"$outroot/bin/geosite.dat" \
"$outroot/bin/geoip.dat" \
"$outroot/bin/geoip-only-cn-private.dat" \
"$outroot/bin/Country.mmdb" \
"$outroot/bin/geoip.metadb" \
)
for n in "${names[@]}"; do
# If file exists under bin/xray/, move it up to bin/
if [[ -f "$outroot/bin/xray/$n" ]]; then
mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n"
fi
# If file already in bin/, leave it as-is
if [[ -f "$outroot/bin/$n" ]]; then
:
for s in "${srcs[@]}"; do
if [[ -f "$s" ]]; then
mv -f "$s" "$outroot/bin/xray/$(basename "$s")"
fi
done
}
# Download geo/rule assets; then unify to bin/
# Download geo/rule assets; then unify to bin/xray/
download_geo_assets() {
local outroot="$1"
local bin_dir="$outroot/bin"
@ -447,7 +442,7 @@ download_geo_assets() {
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
done
# Unify to bin/
# Unify to bin/xray/
unify_geo_layout "$outroot"
}
@ -485,7 +480,7 @@ download_v2rayn_bundle() {
rm -rf "$nested_dir"
fi
# Unify to bin/
# Unify to bin/xray/
unify_geo_layout "$outroot"
echo "[+] Bundle extracted to $outroot"
@ -615,7 +610,7 @@ Source0: __PKGROOT__.tar.gz
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL, xdg-utils
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL
%description
v2rayN Linux for Red Hat Enterprise Linux
@ -634,13 +629,25 @@ https://github.com/2dust/v2rayN
install -dm0755 %{buildroot}/opt/v2rayN
cp -a * %{buildroot}/opt/v2rayN/
# Launcher (prefer native ELF first, then DLL fallback)
# Launcher (prefer native ELF first, then DLL fallback; also create Geo symlinks for the user)
install -dm0755 %{buildroot}%{_bindir}
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
#!/usr/bin/bash
set -euo pipefail
DIR="/opt/v2rayN"
# --- Symlink GEO files into user's XDG dir (first-run convenience) ---
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
USR_GEO_DIR="$XDG_DATA_HOME/v2rayN/bin"
SYS_XRAY_DIR="$DIR/bin/xray"
mkdir -p "$USR_GEO_DIR"
for f in geosite.dat geoip.dat geoip-only-cn-private.dat Country.mmdb; do
if [[ -f "$SYS_XRAY_DIR/$f" && ! -e "$USR_GEO_DIR/$f" ]]; then
ln -s "$SYS_XRAY_DIR/$f" "$USR_GEO_DIR/$f" || true
fi
done
# --- end GEO ---
# Prefer native apphost
if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi

37
pkg2appimage.yml Normal file
View file

@ -0,0 +1,37 @@
app: v2rayN
binpatch: true
ingredients:
script:
- export FileName="v2rayN-${AppImageOutputArch}.zip"
- wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/${FileName}"
- 7z x $FileName -aoa
- cp -rf v2rayN-${AppImageOutputArch}/* $OutputPath
script:
- mkdir -p usr/bin usr/lib
- cp -rf $OutputPath usr/lib/v2rayN
- echo "When this file exists, app will not store configs under this folder" > usr/lib/v2rayN/NotStoreConfigHere.txt
- ln -sf usr/lib/v2rayN/v2rayN usr/bin/v2rayN
- chmod a+x usr/lib/v2rayN/v2rayN
- find usr -type f -exec sh -c 'file "{}" | grep -qi "executable" && chmod +x "{}"' \;
- install -Dm644 usr/lib/v2rayN/v2rayN.png v2rayN.png
- install -Dm644 usr/lib/v2rayN/v2rayN.png usr/share/pixmaps/v2rayN.png
- cat > v2rayN.desktop <<EOF
- [Desktop Entry]
- Name=v2rayN
- Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
- Exec=v2rayN
- Icon=v2rayN
- Terminal=false
- Type=Application
- Categories=Network;
- EOF
- install -Dm644 v2rayN.desktop usr/share/applications/v2rayN.desktop
- cat > AppRun <<\EOF
- #!/bin/sh
- HERE="$(dirname "$(readlink -f "${0}")")"
- cd ${HERE}/usr/lib/v2rayN
- exec ${HERE}/usr/lib/v2rayN/v2rayN $@
- EOF
- chmod a+x AppRun

View file

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.14.9</Version>
<Version>7.14.7</Version>
</PropertyGroup>
<PropertyGroup>

View file

@ -582,9 +582,9 @@ public class Utils
if (host.StartsWith("#"))
continue;
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (hostItem.Length < 2)
if (hostItem.Length != 2)
continue;
systemHosts.Add(hostItem[1], hostItem[0]);
systemHosts.Add(hostItem.Last(), hostItem.First());
}
}
}

View file

@ -12,9 +12,5 @@ public enum EConfigType
TUIC = 8,
WireGuard = 9,
HTTP = 10,
Anytls = 11,
Group = 1000,
PolicyGroup = 1001,
ProxyChain = 1002,
Anytls = 11
}

View file

@ -2,9 +2,8 @@ namespace ServiceLib.Enums;
public enum EMultipleLoad
{
LeastPing,
Fallback,
Random,
RoundRobin,
LeastPing,
LeastLoad
}

View file

@ -24,7 +24,6 @@ public enum EViewAction
RoutingRuleDetailsWindow,
AddServerWindow,
AddServer2Window,
AddGroupServerWindow,
DNSSettingWindow,
RoutingSettingWindow,
OptionSettingWindow,

View file

@ -40,7 +40,6 @@ 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";
@ -50,7 +49,6 @@ 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";

View file

@ -113,10 +113,6 @@ 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)
@ -357,11 +353,6 @@ 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);
@ -1079,35 +1070,6 @@ public static class ConfigHandler
return 0;
}
public static async Task<int> AddGroupServerCommon(Config config, ProfileItem profileItem, ProfileGroupItem profileGroupItem, bool toFile = true)
{
var maxSort = -1;
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.IndexId = Utils.GetGuid(false);
maxSort = ProfileExManager.Instance.GetMaxSort();
}
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
@ -1179,7 +1141,7 @@ public static class ConfigHandler
}
/// <summary>
/// Create a group server that combines multiple servers for load balancing
/// Create a custom server that combines multiple servers for load balancing
/// Generates a configuration file that references multiple servers
/// </summary>
/// <param name="config">Current configuration</param>
@ -1187,55 +1149,45 @@ 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> AddGroupServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad, string? subId)
public static async Task<RetResult> AddCustomServer4Multiple(Config config, List<ProfileItem> selecteds, ECoreType coreType, EMultipleLoad multipleLoad)
{
var result = new RetResult();
var indexId = Utils.GetMd5(Global.CoreMultipleLoadConfigFileName);
var configPath = Utils.GetConfigPath(Global.CoreMultipleLoadConfigFileName);
var indexId = Utils.GetGuid(false);
var childProfileIndexId = Utils.List2String(selecteds.Select(p => p.IndexId).ToList());
var result = await CoreConfigHandler.GenerateClientMultipleLoadConfig(config, configPath, selecteds, coreType, multipleLoad);
if (result.Success != true)
{
return result;
}
var remark = string.Empty;
if (!File.Exists(configPath))
{
return result;
}
var profileItem = await AppManager.Instance.GetProfileItem(indexId) ?? new();
profileItem.IndexId = indexId;
if (coreType == ECoreType.Xray)
{
remark = multipleLoad switch
profileItem.Remarks = multipleLoad switch
{
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerXrayFallback,
EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom,
EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad,
_ => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
EMultipleLoad.Random => ResUI.menuSetDefaultMultipleServerXrayRandom,
EMultipleLoad.RoundRobin => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
EMultipleLoad.LeastPing => ResUI.menuSetDefaultMultipleServerXrayLeastPing,
EMultipleLoad.LeastLoad => ResUI.menuSetDefaultMultipleServerXrayLeastLoad,
_ => ResUI.menuSetDefaultMultipleServerXrayRoundRobin,
};
}
else if (coreType == ECoreType.sing_box)
{
remark = multipleLoad switch
{
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback,
_ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
};
profileItem.Remarks = ResUI.menuSetDefaultMultipleServerSingBoxLeastPing;
}
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;
profileItem.Address = Global.CoreMultipleLoadConfigFileName;
profileItem.ConfigType = EConfigType.Custom;
profileItem.CoreType = coreType;
await AddServerCommon(config, profileItem, true);
result.Data = indexId;
return result;
}
@ -1251,7 +1203,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 && node.ConfigType < EConfigType.Group && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
{
itemSocks = new ProfileItem()
{
@ -1262,7 +1214,7 @@ public static class ConfigHandler
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
};
}
else if ((node.ConfigType == EConfigType.Custom && node.ConfigType < EConfigType.Group && node.PreSocksPort > 0))
else if ((node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0))
{
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem()
@ -2269,7 +2221,6 @@ public static class ConfigHandler
UseSystemHosts = false,
AddCommonHosts = true,
FakeIP = false,
GlobalFakeIp = true,
BlockBindingQuery = true,
DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(),
RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(),

View file

@ -132,4 +132,24 @@ 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;
}
}

View file

@ -67,7 +67,6 @@ public sealed class AppManager
SQLiteHelper.Instance.CreateTable<ProfileExItem>();
SQLiteHelper.Instance.CreateTable<DNSItem>();
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
SQLiteHelper.Instance.CreateTable<ProfileGroupItem>();
return true;
}
@ -102,7 +101,6 @@ 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();
@ -221,15 +219,6 @@ 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();

View file

@ -1,167 +0,0 @@
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);
}
}
}

View file

@ -35,7 +35,6 @@ public class TaskManager
await ConfigHandler.SaveConfig(_config);
await ProfileExManager.Instance.SaveTo();
await ProfileGroupItemManager.Instance.SaveTo();
}
//Execute once 1 hour

View file

@ -1,13 +1,10 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.Models;
public class CheckUpdateModel : ReactiveObject
public class CheckUpdateModel
{
public bool? IsSelected { get; set; }
public string? CoreType { get; set; }
[Reactive] public string? Remarks { get; set; }
public string? Remarks { get; set; }
public string? FileName { get; set; }
public bool? IsFinished { get; set; }
}

View file

@ -1,10 +1,7 @@
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.Models;
[Serializable]
public class ClashProxyModel : ReactiveObject
public class ClashProxyModel
{
public string? Name { get; set; }
@ -12,9 +9,9 @@ public class ClashProxyModel : ReactiveObject
public string? Now { get; set; }
[Reactive] public int Delay { get; set; }
public int Delay { get; set; }
[Reactive] public string? DelayName { get; set; }
public string? DelayName { get; set; }
public bool IsActive { get; set; }
}

View file

@ -260,7 +260,6 @@ 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; }

View file

@ -1,13 +0,0 @@
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;
}

View file

@ -39,14 +39,11 @@ public class ProfileItem : ReactiveObject
> 1 => $"***{arrAddr.Last()}",
_ => Address
};
if (ConfigType is EConfigType.Custom or > EConfigType.Group)
summary += ConfigType switch
{
summary += $"[{CoreType.ToString()}]{Remarks}";
}
else
{
summary += $"{Remarks}({addr}:{Port})";
}
EConfigType.Custom => $"[{CoreType.ToString()}]{Remarks}",
_ => $"{Remarks}({addr}:{Port})"
};
return summary;
}

View file

@ -145,7 +145,6 @@ 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

View file

@ -294,15 +294,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Group &apos;{0}&apos; is empty. Please add at least one node. 的本地化字符串。
/// </summary>
public static string GroupEmpty {
get {
return ResourceManager.GetString("GroupEmpty", resourceCulture);
}
}
/// <summary>
/// 查找类似 This is not the correct configuration, please check 的本地化字符串。
/// </summary>
@ -708,15 +699,6 @@ 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>
@ -744,24 +726,6 @@ 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>
@ -1014,78 +978,6 @@ 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>
@ -1455,15 +1347,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Remove Child Configuration 的本地化字符串。
/// </summary>
public static string menuRemoveChildServer {
get {
return ResourceManager.GetString("menuRemoveChildServer", resourceCulture);
}
}
/// <summary>
/// 查找类似 Remove duplicate Configurations 的本地化字符串。
/// </summary>
@ -1617,15 +1500,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Server List 的本地化字符串。
/// </summary>
public static string menuServerList {
get {
return ResourceManager.GetString("menuServerList", resourceCulture);
}
}
/// <summary>
/// 查找类似 Configurations 的本地化字符串。
/// </summary>
@ -1635,6 +1509,60 @@ 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>
@ -2040,15 +1968,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Node alias &apos;{0}&apos; does not exist. 的本地化字符串。
/// </summary>
public static string NodeTagNotExist {
get {
return ResourceManager.GetString("NodeTagNotExist", resourceCulture);
}
}
/// <summary>
/// 查找类似 Non-VMess or SS protocol 的本地化字符串。
/// </summary>
@ -2103,15 +2022,6 @@ 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>
@ -2158,20 +2068,20 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Policy group: 的本地化字符串。
/// 查找类似 Proxy chained node alias &apos;{0}&apos; does not exist. 的本地化字符串。
/// </summary>
public static string PolicyGroupPrefix {
public static string ProxyChainedNodeTagNotExist {
get {
return ResourceManager.GetString("PolicyGroupPrefix", resourceCulture);
return ResourceManager.GetString("ProxyChainedNodeTagNotExist", resourceCulture);
}
}
/// <summary>
/// 查找类似 Proxy chained: 的本地化字符串。
/// 查找类似 Proxy chained node remark &apos;{0}&apos; refers to an outbound that does not support config type &apos;{1}&apos;. 的本地化字符串。
/// </summary>
public static string ProxyChainedPrefix {
public static string ProxyChainedNodeTagNotSupportConfigType {
get {
return ResourceManager.GetString("ProxyChainedPrefix", resourceCulture);
return ResourceManager.GetString("ProxyChainedNodeTagNotSupportConfigType", resourceCulture);
}
}
@ -2239,11 +2149,20 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Routing rule outbound: 的本地化字符串。
/// 查找类似 Routing rule references outbound remark &apos;{0}&apos;, but no outbound with this remark exists. 的本地化字符串。
/// </summary>
public static string RoutingRuleOutboundPrefix {
public static string RoutingRuleOutboundTagNotExist {
get {
return ResourceManager.GetString("RoutingRuleOutboundPrefix", resourceCulture);
return ResourceManager.GetString("RoutingRuleOutboundTagNotExist", resourceCulture);
}
}
/// <summary>
/// 查找类似 Routing rule outbound remark &apos;{0}&apos; refers to an outbound that does not support config type &apos;{1}&apos;. 的本地化字符串。
/// </summary>
public static string RoutingRuleOutboundTagNotSupportConfigType {
get {
return ResourceManager.GetString("RoutingRuleOutboundTagNotSupportConfigType", resourceCulture);
}
}
@ -2445,6 +2364,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Apply to Proxy Domains Only 的本地化字符串。
/// </summary>
public static string TbApplyProxyDomainsOnly {
get {
return ResourceManager.GetString("TbApplyProxyDomainsOnly", resourceCulture);
}
}
/// <summary>
/// 查找类似 Auto refresh 的本地化字符串。
/// </summary>
@ -2517,24 +2445,6 @@ 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>
@ -2679,24 +2589,6 @@ 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>
@ -2814,24 +2706,6 @@ 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>
@ -2886,15 +2760,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Policy Group Type 的本地化字符串。
/// </summary>
public static string TbPolicyGroupType {
get {
return ResourceManager.GetString("TbPolicyGroupType", resourceCulture);
}
}
/// <summary>
/// 查找类似 Port 的本地化字符串。
/// </summary>
@ -2967,15 +2832,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Random 的本地化字符串。
/// </summary>
public static string TbRandom {
get {
return ResourceManager.GetString("TbRandom", resourceCulture);
}
}
/// <summary>
/// 查找类似 v2ray Full Config Template 的本地化字符串。
/// </summary>
@ -3039,15 +2895,6 @@ 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>

View file

@ -1377,22 +1377,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>مخفی و پورت می شود، با کاما (،) جدا می شود</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>چند سرور به پیکربندی سفارشی</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<value>چند سرور تصادفی توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<value>چند سرور RoundRobin توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<value>چند سرور LeastPing توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<value>چند سرور LeastLoad توسط Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>LeastPing چند سرور توسط sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@ -1455,6 +1455,9 @@
<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,57 +1515,6 @@
<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>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
@ -1572,19 +1524,16 @@
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>Routing rule references outbound remark '{0}', but no outbound with this remark exists.</value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>Proxy chained node remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>Proxy chained node alias '{0}' does not exist.</value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>Routing rule outbound remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
</root>

View file

@ -1377,22 +1377,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>A portot lefedi, vesszővel (,) elválasztva</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>Több konfiguráció egyéni konfigurációra</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<value>Több konfiguráció véletlenszerűen Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Több konfiguráció RoundRobin Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<value>Több konfiguráció legkisebb pinggel Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Több konfiguráció legkisebb terheléssel Xray szerint</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Több konfiguráció legkisebb pinggel sing-box szerint</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@ -1455,6 +1455,9 @@
<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,57 +1515,6 @@
<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>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
@ -1572,19 +1524,16 @@
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>Routing rule references outbound remark '{0}', but no outbound with this remark exists.</value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>Proxy chained node remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>Proxy chained node alias '{0}' does not exist.</value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>Routing rule outbound remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
</root>

View file

@ -1377,22 +1377,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>Will cover the port, separate with commas (,)</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>Multi-Configuration to custom configuration</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<value>Multi-Configuration Random by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Multi-Configuration RoundRobin by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<value>Multi-Configuration LeastPing by Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Multi-Configuration LeastLoad by Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Multi-Configuration LeastPing by sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@ -1455,6 +1455,9 @@
<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,57 +1515,6 @@
<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>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
@ -1572,19 +1524,16 @@
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>Routing rule references outbound remark '{0}', but no outbound with this remark exists.</value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>Proxy chained node remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>Proxy chained node alias '{0}' does not exist.</value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>Routing rule outbound remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
</root>

View file

@ -1377,22 +1377,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>Заменит указанный порт, перечисляйте через запятую (,)</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>От мультиконфигурации к пользовательской конфигурации</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<value>Случайный (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<value>Круговой (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<value>Минимальное RTT (минимальное время туда-обратно) (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<value>Минимальная нагрузка (Xray)</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>Минимальное RTT (минимальное время туда-обратно) (sing-box)</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@ -1455,6 +1455,9 @@
<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>
@ -1512,57 +1515,6 @@
<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>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
@ -1572,19 +1524,16 @@
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>Routing rule references outbound remark '{0}', but no outbound with this remark exists.</value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>Proxy chained node remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>Proxy chained node alias '{0}' does not exist.</value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>Routing rule outbound remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
</root>

View file

@ -1374,22 +1374,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>会覆盖端口,多组时用逗号 (,) 隔开</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>多配置文件生成策略组</value>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>多配置文件产生自定义配置 (多选)</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<value>多配置文件随机 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多配置文件负载均衡 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<value>多配置文件最低延迟 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多配置文件最稳定 Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多配置文件最低延迟 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@ -1452,6 +1452,9 @@
<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>
@ -1509,57 +1512,6 @@
<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>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>核心 '{0}' 不支持网络类型 '{1}'。</value>
</data>
@ -1569,19 +1521,16 @@
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>核心 '{0}' 不支持协议 '{1}'。</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>代理链: </value>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>路由规则引用了出站别名 '{0}',但不存在具有该别名的出站。</value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>路由规则出站: </value>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>代理链节点别名 '{0}' 引用的出站不支持配置类型 '{1}'。</value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>策略组: </value>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>代理链节点别名 '{0}' 不存在。</value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>节点别名 '{0}' 不存在。</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>组“{0}”为空。请至少添加一个节点。</value>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>路由规则出站别名 '{0}' 引用的出站不支持配置类型 '{1}'。</value>
</data>
</root>

View file

@ -1374,22 +1374,22 @@
<data name="TbPorts7Tips" xml:space="preserve">
<value>會覆蓋埠,多組時用逗號 (,) 隔開</value>
</data>
<data name="menuGenGroupMultipleServer" xml:space="preserve">
<value>Generate Policy Group from Multiple Profiles</value>
<data name="menuSetDefaultMultipleServer" xml:space="preserve">
<value>多設定檔產生自訂配置 (多選)</value>
</data>
<data name="menuGenGroupMultipleServerXrayRandom" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRandom" xml:space="preserve">
<value>多設定檔隨機 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayRoundRobin" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayRoundRobin" xml:space="preserve">
<value>多設定檔負載平衡 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastPing" xml:space="preserve">
<value>多設定檔最低延遲 Xray</value>
</data>
<data name="menuGenGroupMultipleServerXrayLeastLoad" xml:space="preserve">
<data name="menuSetDefaultMultipleServerXrayLeastLoad" xml:space="preserve">
<value>多設定檔最穩定 Xray</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxLeastPing" xml:space="preserve">
<data name="menuSetDefaultMultipleServerSingBoxLeastPing" xml:space="preserve">
<value>多設定檔最低延遲 sing-box</value>
</data>
<data name="menuExportConfig" xml:space="preserve">
@ -1452,6 +1452,9 @@
<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>
@ -1509,57 +1512,6 @@
<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>
<data name="CoreNotSupportNetwork" xml:space="preserve">
<value>Core '{0}' does not support network type '{1}'.</value>
</data>
@ -1569,19 +1521,16 @@
<data name="CoreNotSupportProtocol" xml:space="preserve">
<value>Core '{0}' does not support protocol '{1}'.</value>
</data>
<data name="ProxyChainedPrefix" xml:space="preserve">
<value>Proxy chained: </value>
<data name="RoutingRuleOutboundTagNotExist" xml:space="preserve">
<value>Routing rule references outbound remark '{0}', but no outbound with this remark exists.</value>
</data>
<data name="RoutingRuleOutboundPrefix" xml:space="preserve">
<value>Routing rule outbound: </value>
<data name="ProxyChainedNodeTagNotSupportConfigType" xml:space="preserve">
<value>Proxy chained node remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
<data name="PolicyGroupPrefix" xml:space="preserve">
<value>Policy group: </value>
<data name="ProxyChainedNodeTagNotExist" xml:space="preserve">
<value>Proxy chained node alias '{0}' does not exist.</value>
</data>
<data name="NodeTagNotExist" xml:space="preserve">
<value>Node alias '{0}' does not exist.</value>
</data>
<data name="GroupEmpty" xml:space="preserve">
<value>Group '{0}' is empty. Please add at least one node.</value>
<data name="RoutingRuleOutboundTagNotSupportConfigType" xml:space="preserve">
<value>Routing rule outbound remark '{0}' refers to an outbound that does not support config type '{1}'.</value>
</data>
</root>

View file

@ -1,92 +0,0 @@
{
"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"
]
}

View file

@ -44,7 +44,6 @@
<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>

View file

@ -8,114 +8,92 @@ namespace ServiceLib.Services;
/// <summary>
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
/// Return (ok, msg) for VMs to decide.
/// </summary>
public class ActionPrecheckService(Config config)
public class ActionPrecheckService
{
private static readonly Lazy<ActionPrecheckService> _instance = new(() => new ActionPrecheckService(AppManager.Instance.Config));
public static ActionPrecheckService Instance => _instance.Value;
private readonly Config _config = config;
private readonly Config _config;
public async Task<List<string>> CheckBeforeSetActive(string? indexId)
public ActionPrecheckService(Config config)
{
_config = config;
}
private static List<string> OneMsg(string msg) => new() { msg };
public async Task<(bool ok, List<string> msgs)> CheckBeforeSetActive(string? indexId)
{
if (indexId.IsNullOrEmpty())
{
return [ResUI.PleaseSelectServer];
return (false, OneMsg(ResUI.PleaseSelectServer));
}
var item = await AppManager.Instance.GetProfileItem(indexId);
if (item is null)
{
return [ResUI.PleaseSelectServer];
return (false, OneMsg(ResUI.PleaseSelectServer));
}
return await CheckBeforeGenerateConfig(item);
}
public async Task<List<string>> CheckBeforeGenerateConfig(ProfileItem? item)
public async Task<(bool ok, List<string> msgs)> CheckBeforeGenerateConfig(ProfileItem? item)
{
if (item is null)
{
return [ResUI.PleaseSelectServer];
return (false, OneMsg(ResUI.PleaseSelectServer));
}
var errors = new List<string>();
var msgs = new List<string>();
errors.AddRange(await ValidateCurrentNodeAndCoreSupport(item));
errors.AddRange(await ValidateRelatedNodesExistAndValid(item));
return errors;
}
private async Task<List<string>> ValidateCurrentNodeAndCoreSupport(ProfileItem item)
{
if (item.ConfigType == EConfigType.Custom)
var currentNodeMsgs = ValidateCurrentNodeAndCoreSupport(item).ToList();
if (currentNodeMsgs.Count > 0)
{
return [];
msgs.AddRange(currentNodeMsgs);
return (false, msgs);
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
return await ValidateNodeAndCoreSupport(item, coreType);
msgs.AddRange(await ValidateRelatedNodesExistAndValid(item));
return (true, msgs);
}
private async Task<List<string>> ValidateNodeAndCoreSupport(ProfileItem item, ECoreType? coreType = null)
private IEnumerable<string> ValidateCurrentNodeAndCoreSupport(ProfileItem item)
{
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
return ValidateNodeAndCoreSupport(item, coreType);
}
/// <summary>
/// <summary>
/// Validate whether the node and chosen core combination is supported. Returns a collection of messages to show the user.
/// An empty collection means there are no blocking errors.
/// </summary>
///
private IEnumerable<string> ValidateNodeAndCoreSupport(ProfileItem item, ECoreType? coreType = null)
{
var errors = new List<string>();
// sing-box does not support xhttp / kcp
// sing-box does not support transports like ws/http/httpupgrade/etc. when the node is not vmess/trojan/vless
coreType ??= AppManager.Instance.GetCoreType(item, item.ConfigType);
if (item.ConfigType is EConfigType.Custom)
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, coreType.ToString(), item.ConfigType.ToString()));
return errors;
}
if (item.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.ChildItems.IsNullOrEmpty())
{
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
return errors;
}
foreach (var child in Utils.String2List(group.ChildItems))
{
if (child.IsNullOrEmpty())
{
continue;
}
var childItem = await AppManager.Instance.GetProfileItem(child);
if (childItem is null)
{
errors.Add(string.Format(ResUI.NodeTagNotExist, child));
continue;
}
var childErrors = await ValidateNodeAndCoreSupport(childItem, coreType);
errors.AddRange(childErrors);
}
return errors;
}
var net = item.GetNetwork() ?? item.Network;
if (coreType == ECoreType.sing_box)
{
if (net is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
{
errors.Add(string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net));
return errors;
yield return string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net);
yield break;
}
if (item.ConfigType is not (EConfigType.VMess or EConfigType.VLESS or EConfigType.Trojan))
{
if (net is nameof(ETransport.ws) or nameof(ETransport.http) or nameof(ETransport.h2) or nameof(ETransport.quic) or nameof(ETransport.httpupgrade))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), item.ConfigType.ToString(), net));
return errors;
yield return string.Format(ResUI.CoreNotSupportProtocolTransport, nameof(ECoreType.sing_box), item.ConfigType.ToString(), net);
yield break;
}
}
}
@ -124,74 +102,80 @@ public class ActionPrecheckService(Config config)
// Xray core does not support these protocols
if (item.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
return errors;
yield return string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString());
yield break;
}
}
return errors;
yield break; // explicit for clarity; no blocking errors
}
private async Task<List<string>> ValidateRelatedNodesExistAndValid(ProfileItem? item)
/// <summary>
/// Validate that nodes related to the current node (chained/routing) exist and are valid.
/// </summary>
private async Task<IEnumerable<string>> ValidateRelatedNodesExistAndValid(ProfileItem? item)
{
var errors = new List<string>();
errors.AddRange(await ValidateProxyChainedNodeExistAndValid(item));
errors.AddRange(await ValidateRoutingNodeExistAndValid(item));
return errors;
var msgs = new List<string>();
msgs.AddRange(await ValidateProxyChainedNodeExistAndValid(item));
msgs.AddRange(await ValidateRoutingNodeExistAndValid(item));
return msgs;
}
private async Task<List<string>> ValidateProxyChainedNodeExistAndValid(ProfileItem? item)
private async Task<IEnumerable<string>> ValidateProxyChainedNodeExistAndValid(ProfileItem? item)
{
var errors = new List<string>();
var msgs = new List<string>();
if (item is null)
{
return errors;
return msgs;
}
// prev node and next node
var subItem = await AppManager.Instance.GetSubItem(item.Subid);
if (subItem is null)
{
return errors;
return msgs;
}
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
await CollectProxyChainedNodeValidation(prevNode, subItem.PrevProfile, coreType, errors);
await CollectProxyChainedNodeValidation(nextNode, subItem.NextProfile, coreType, errors);
CollectProxyChainedNodeValidation(prevNode, subItem.PrevProfile, coreType, msgs);
CollectProxyChainedNodeValidation(nextNode, subItem.NextProfile, coreType, msgs);
return errors;
return msgs;
}
private async Task CollectProxyChainedNodeValidation(ProfileItem? node, string tag, ECoreType coreType, List<string> errors)
private void CollectProxyChainedNodeValidation(ProfileItem? node, string tag, ECoreType coreType, List<string> msgs)
{
if (node is not null)
{
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
msgs.AddRange(ValidateNodeAndCoreSupport(node, coreType));
if (node.ConfigType is EConfigType.Custom)
{
msgs.Add(string.Format(ResUI.ProxyChainedNodeTagNotSupportConfigType, node.Remarks, node.ConfigType.ToString()));
}
}
else if (tag.IsNotEmpty())
{
errors.Add(ResUI.ProxyChainedPrefix + string.Format(ResUI.NodeTagNotExist, tag));
msgs.Add(string.Format(ResUI.ProxyChainedNodeTagNotExist, tag));
}
}
private async Task<List<string>> ValidateRoutingNodeExistAndValid(ProfileItem? item)
private async Task<IEnumerable<string>> ValidateRoutingNodeExistAndValid(ProfileItem? item)
{
var errors = new List<string>();
var msgs = new List<string>();
if (item is null)
{
return errors;
return msgs;
}
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
var routing = await ConfigHandler.GetDefaultRouting(_config);
if (routing == null)
{
return errors;
return msgs;
}
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
@ -211,14 +195,17 @@ public class ActionPrecheckService(Config config)
var tagItem = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
if (tagItem is null)
{
errors.Add(ResUI.RoutingRuleOutboundPrefix + string.Format(ResUI.NodeTagNotExist, outboundTag));
msgs.Add(string.Format(ResUI.RoutingRuleOutboundTagNotExist, outboundTag));
continue;
}
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
msgs.AddRange(ValidateNodeAndCoreSupport(tagItem, coreType));
if (tagItem.ConfigType is EConfigType.Custom)
{
msgs.Add(string.Format(ResUI.RoutingRuleOutboundTagNotSupportConfigType, outboundTag, tagItem.ConfigType.ToString()));
}
}
return errors;
return msgs;
}
}

View file

@ -15,33 +15,6 @@ 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)
{
@ -371,105 +344,7 @@ public partial class CoreConfigSingboxService(Config config)
}
}
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)
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds)
{
var ret = new RetResult();
try
@ -544,7 +419,7 @@ public partial class CoreConfigSingboxService(Config config)
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenChainOutboundsList(proxyProfiles, singboxConfig);
await GenOutboundsList(proxyProfiles, singboxConfig);
await GenDns(null, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);

View file

@ -33,15 +33,6 @@ 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))
@ -206,28 +197,6 @@ 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;
@ -307,12 +276,10 @@ public partial class CoreConfigSingboxService
}
else
{
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
if (simpleDNSItem.FakeIP == true)
{
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;

View file

@ -410,7 +410,7 @@ public partial class CoreConfigSingboxService
return 0;
}
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag)
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig)
{
try
{
@ -438,38 +438,6 @@ 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);
@ -482,7 +450,7 @@ public partial class CoreConfigSingboxService
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
// current proxy
currentServer.tag = $"{baseTagName}-{index}";
currentServer.tag = $"{Global.ProxyTag}-{index}";
proxyTags.Add(currentServer.tag);
if (!node.Subid.IsNullOrEmpty())
@ -499,7 +467,7 @@ public partial class CoreConfigSingboxService
{
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
prevTag = $"prev-{baseTagName}-{++prevIndex}";
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
prevOutbound.tag = prevTag;
prevOutbounds.Add(prevOutbound);
}
@ -540,21 +508,16 @@ public partial class CoreConfigSingboxService
var outUrltest = new Outbound4Sbox
{
type = "urltest",
tag = $"{baseTagName}-auto",
tag = $"{Global.ProxyTag}-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 = baseTagName,
tag = Global.ProxyTag,
outbounds = JsonUtils.DeepCopy(proxyTags),
interrupt_exist_connections = false,
};
@ -611,52 +574,4 @@ 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);
}
}

View file

@ -337,60 +337,19 @@ public partial class CoreConfigSingboxService
}
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
if (node == null
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
&& node.ConfigType is not (EConfigType.PolicyGroup or EConfigType.ProxyChain)))
|| !Global.SingboxSupportConfigType.Contains(node.ConfigType))
{
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 = tag;
server.tag = Global.ProxyTag + node.IndexId.ToString();
if (server is Endpoints4Sbox endpoint)
{
singboxConfig.endpoints ??= new();

View file

@ -15,33 +15,6 @@ 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)
{
@ -126,22 +99,17 @@ public partial class CoreConfigV2rayService(Config config)
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);
v2rayConfig.outbounds.RemoveAt(0);
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;
@ -183,37 +151,18 @@ public partial class CoreConfigV2rayService(Config config)
await GenOutboundsList(proxyProfiles, v2rayConfig);
//add balancers
await GenObservatory(v2rayConfig, multipleLoad);
await GenBalancer(v2rayConfig, multipleLoad);
var defaultBalancerTag = $"{Global.ProxyTag}{Global.BalancerTagSuffix}";
var balancer = v2rayConfig.routing.balancers.First();
//add rule
var rules = v2rayConfig.routing.rules;
var rules = v2rayConfig.routing.rules.Where(t => t.outboundTag == Global.ProxyTag).ToList();
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;
}
rule.outboundTag = null;
rule.balancerTag = balancer.tag;
}
}
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
@ -221,7 +170,7 @@ public partial class CoreConfigV2rayService(Config config)
v2rayConfig.routing.rules.Add(new()
{
ip = ["0.0.0.0/0", "::/0"],
balancerTag = defaultBalancerTag,
balancerTag = balancer.tag,
type = "field"
});
}
@ -230,7 +179,7 @@ public partial class CoreConfigV2rayService(Config config)
v2rayConfig.routing.rules.Add(new()
{
network = "tcp,udp",
balancerTag = defaultBalancerTag,
balancerTag = balancer.tag,
type = "field"
});
}
@ -248,98 +197,6 @@ public partial class CoreConfigV2rayService(Config config)
}
}
public async Task<RetResult> GenerateClientChainConfig(List<ProfileItem> selecteds)
{
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;
}
await GenLog(v2rayConfig);
await GenInbounds(v2rayConfig);
await GenRouting(v2rayConfig);
await GenDns(null, v2rayConfig);
await GenStatistic(v2rayConfig);
v2rayConfig.outbounds.RemoveAt(0);
var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds)
{
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 GenChainOutboundsList(proxyProfiles, v2rayConfig);
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> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
{
var ret = new RetResult();

View file

@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService
{
private async Task<int> GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
private async Task<int> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
{
if (multipleLoad == EMultipleLoad.LeastPing)
{
@ -15,7 +15,7 @@ public partial class CoreConfigV2rayService
};
v2rayConfig.observatory = observatory;
}
else if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback)
else if (multipleLoad == EMultipleLoad.LeastLoad)
{
var burstObservatory = new BurstObservatory4Ray
{
@ -30,11 +30,6 @@ 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",
@ -43,22 +38,13 @@ public partial class CoreConfigV2rayService
EMultipleLoad.LeastLoad => "leastLoad",
_ => "roundRobin",
};
var balancerTag = $"{selector}{Global.BalancerTagSuffix}";
var balancer = new BalancersItem4Ray
{
selector = [selector],
strategy = new()
{
type = strategyType,
settings = new()
{
expected = 1,
},
},
tag = balancerTag,
selector = [Global.ProxyTag],
strategy = new() { type = strategyType },
tag = $"{Global.ProxyTag}-round",
};
v2rayConfig.routing.balancers ??= new();
v2rayConfig.routing.balancers.Add(balancer);
return await Task.FromResult(balancerTag);
v2rayConfig.routing.balancers = [balancer];
return await Task.FromResult(0);
}
}

View file

@ -552,7 +552,7 @@ public partial class CoreConfigV2rayService
return 0;
}
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag)
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig)
{
try
{
@ -577,34 +577,6 @@ 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);
@ -618,7 +590,7 @@ public partial class CoreConfigV2rayService
// current proxy
await GenOutbound(node, currentOutbound);
currentOutbound.tag = $"{baseTagName}-{index}";
currentOutbound.tag = $"{Global.ProxyTag}-{index}";
if (!node.Subid.IsNullOrEmpty())
{
@ -634,7 +606,7 @@ public partial class CoreConfigV2rayService
{
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(prevNode, prevOutbound);
prevTag = $"prev-{baseTagName}-{++prevIndex}";
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
prevOutbound.tag = prevTag;
prevOutbounds.Add(prevOutbound);
}
@ -720,50 +692,4 @@ 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);
}
}

View file

@ -125,60 +125,16 @@ public partial class CoreConfigV2rayService
}
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
if (node == null
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
&& node.ConfigType is not (EConfigType.PolicyGroup or EConfigType.ProxyChain)))
|| !Global.XraySupportConfigType.Contains(node.ConfigType))
{
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 = tag;
outbound.tag = Global.ProxyTag + node.IndexId.ToString();
v2rayConfig.outbounds.Add(outbound);
return outbound.tag;

View file

@ -1,222 +0,0 @@
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;
RemoveCmd = ReactiveCommand.CreateFromTask(async () =>
{
await ChildRemoveAsync();
});
MoveTopCmd = ReactiveCommand.CreateFromTask(async () =>
{
await MoveServer(EMove.Top);
});
MoveUpCmd = ReactiveCommand.CreateFromTask(async () =>
{
await MoveServer(EMove.Up);
});
MoveDownCmd = ReactiveCommand.CreateFromTask(async () =>
{
await MoveServer(EMove.Down);
});
MoveBottomCmd = ReactiveCommand.CreateFromTask(async () =>
{
await MoveServer(EMove.Bottom);
});
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);
}
}
}

View file

@ -1,6 +1,7 @@
using System.Reactive;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Splat;
namespace ServiceLib.ViewModels;

View file

@ -334,6 +334,9 @@ public class CheckUpdateViewModel : MyReactiveObject
{
return;
}
found.Remarks = model.Remarks;
var itemCopy = JsonUtils.DeepCopy(found);
itemCopy.Remarks = model.Remarks;
CheckUpdateModels.Replace(found, itemCopy);
}
}

View file

@ -391,6 +391,7 @@ public class ClashProxiesViewModel : MyReactiveObject
public async Task ProxiesDelayTestResult(SpeedTestResult result)
{
//UpdateHandler(false, $"{item.name}={result}");
var detail = ProxyDetails.FirstOrDefault(it => it.Name == result.IndexId);
if (detail == null)
{
@ -413,6 +414,7 @@ public class ClashProxiesViewModel : MyReactiveObject
detail.Delay = _delayTimeout;
detail.DelayName = string.Empty;
}
ProxyDetails.Replace(detail, JsonUtils.DeepCopy(detail));
}
#endregion proxy function

View file

@ -23,8 +23,6 @@ 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; }
@ -124,14 +122,6 @@ 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);
@ -238,7 +228,6 @@ 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);
@ -246,7 +235,6 @@ public class MainWindowViewModel : MyReactiveObject
{
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
}
await RefreshServers();
BlReloadEnabled = true;
await Reload();
@ -333,10 +321,6 @@ 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);
@ -503,7 +487,7 @@ public class MainWindowViewModel : MyReactiveObject
}
else if (Utils.IsLinux())
{
ProcUtils.ProcessStart("xdg-open", path);
ProcUtils.ProcessStart("nautilus", path);
}
else if (Utils.IsOSX())
{

View file

@ -1,22 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Splat;
namespace ServiceLib.ViewModels;
public class ProfilesSelectViewModel : MyReactiveObject
{
#region private prop
private List<ProfileItem> _lstProfile;
private string _serverFilter = string.Empty;
private Dictionary<string, bool> _dicHeaderSort = new();
private string _subIndexId = string.Empty;
// ConfigType filter state: default include-mode with all types selected
private List<EConfigType> _filterConfigTypes = new();
private bool _filterExclude = false;
#endregion private prop
@ -61,7 +66,6 @@ public class ProfilesSelectViewModel : MyReactiveObject
_config = AppManager.Instance.Config;
_updateView = updateView;
_subIndexId = _config.SubIndexId ?? string.Empty;
#region WhenAnyValue && ReactiveCommand
this.WhenAnyValue(
@ -126,7 +130,6 @@ public class ProfilesSelectViewModel : MyReactiveObject
_updateView?.Invoke(EViewAction.CloseWindow, null);
return true;
}
#endregion Actions
#region Servers && Groups
@ -165,6 +168,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
private async Task RefreshServersBiz()
{
var lstModel = await GetProfileItemsEx(_subIndexId, _serverFilter);
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
ProfileItems.Clear();
ProfileItems.AddRange(lstModel);
@ -207,6 +211,9 @@ public class ProfilesSelectViewModel : MyReactiveObject
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
{
var lstModel = await AppManager.Instance.ProfileItems(_subIndexId, filter);
//await ConfigHandler.SetDefaultServer(_config, lstModel);
lstModel = (from t in lstModel
select new ProfileItemModel
{

View file

@ -53,13 +53,11 @@ 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> 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; }
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; }
//servers move
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
@ -141,33 +139,25 @@ public class ProfilesViewModel : MyReactiveObject
{
await ShareServerAsync();
}, canEditRemove);
GenGroupMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () =>
SetDefaultMultipleServerXrayRandomCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Random);
await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.Random);
}, canEditRemove);
GenGroupMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () =>
SetDefaultMultipleServerXrayRoundRobinCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin);
await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.RoundRobin);
}, canEditRemove);
GenGroupMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
SetDefaultMultipleServerXrayLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing);
await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.LeastPing);
}, canEditRemove);
GenGroupMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () =>
SetDefaultMultipleServerXrayLeastLoadCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad);
await SetDefaultMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad);
}, canEditRemove);
GenGroupMultipleServerXrayFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
SetDefaultMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Fallback);
}, canEditRemove);
GenGroupMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing);
}, canEditRemove);
GenGroupMultipleServerSingBoxFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.Fallback);
await SetDefaultMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing);
}, canEditRemove);
//servers move
@ -267,7 +257,7 @@ public class ProfilesViewModel : MyReactiveObject
SelectedMoveToGroup = new();
await RefreshSubscriptions();
//await RefreshServers();
await RefreshServers();
}
#endregion Init
@ -303,6 +293,7 @@ public class ProfilesViewModel : MyReactiveObject
{
item.SpeedVal = result.Speed ?? string.Empty;
}
//_profileItems.Replace(item, JsonUtils.DeepCopy(item));
}
public async Task UpdateStatistics(ServerSpeedItem update)
@ -323,6 +314,17 @@ public class ProfilesViewModel : MyReactiveObject
item.TodayUp = Utils.HumanFy(update.TodayUp);
item.TotalDown = Utils.HumanFy(update.TotalDown);
item.TotalUp = Utils.HumanFy(update.TotalUp);
//if (SelectedProfile?.IndexId == item.IndexId)
//{
// var temp = JsonUtils.DeepCopy(item);
// _profileItems.Replace(item, temp);
// SelectedProfile = temp;
//}
//else
//{
// _profileItems.Replace(item, JsonUtils.DeepCopy(item));
//}
}
}
catch
@ -501,10 +503,6 @@ 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);
@ -596,10 +594,14 @@ public class ProfilesViewModel : MyReactiveObject
return;
}
var msgs = await ActionPrecheckService.Instance.CheckBeforeSetActive(indexId);
foreach (var msg in msgs)
var (ok, msgs) = await ActionPrecheckService.Instance.CheckBeforeSetActive(indexId);
if (msgs.Count > 0)
{
NoticeManager.Instance.SendMessage(msg);
NoticeManager.Instance.Enqueue(msgs.First());
}
if (!ok)
{
return;
}
if (await ConfigHandler.SetDefaultServerIndex(_config, indexId) == 0)
@ -626,7 +628,7 @@ public class ProfilesViewModel : MyReactiveObject
await _updateView?.Invoke(EViewAction.ShareServer, url);
}
private async Task GenGroupMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad)
private async Task SetDefaultMultipleServer(ECoreType coreType, EMultipleLoad multipleLoad)
{
var lstSelected = await GetProfileItems(true);
if (lstSelected == null)
@ -634,7 +636,7 @@ public class ProfilesViewModel : MyReactiveObject
return;
}
var ret = await ConfigHandler.AddGroupServer4Multiple(_config, lstSelected, coreType, multipleLoad, SelectedSub?.Id);
var ret = await ConfigHandler.AddCustomServer4Multiple(_config, lstSelected, coreType, multipleLoad);
if (ret.Success != true)
{
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
@ -766,10 +768,14 @@ public class ProfilesViewModel : MyReactiveObject
return;
}
var msgs = await ActionPrecheckService.Instance.CheckBeforeGenerateConfig(item);
foreach (var msg in msgs)
var (ok, msgs) = await ActionPrecheckService.Instance.CheckBeforeGenerateConfig(item);
if (msgs.Count > 0)
{
NoticeManager.Instance.SendMessage(msg);
NoticeManager.Instance.Enqueue(msgs.First());
}
if (!ok)
{
return;
}
if (blClipboard)
{

View file

@ -13,7 +13,6 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
[Reactive]
public RoutingItem SelectedRouting { get; set; }
public IObservableCollection<RulesItemModel> RulesItems { get; } = new ObservableCollectionExtended<RulesItemModel>();
[Reactive]

View file

@ -1,151 +0,0 @@
<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>

View file

@ -1,166 +0,0 @@
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();
}
}
}

View file

@ -229,7 +229,7 @@
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFakeIPTips}"
Text="{x:Static resx:ResUI.TbApplyProxyDomainsOnly}"
TextWrapping="Wrap" />
<TextBlock

View file

@ -35,8 +35,6 @@
<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}" />

View file

@ -86,8 +86,6 @@ 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);
@ -206,11 +204,6 @@ 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);

View file

@ -38,10 +38,9 @@
<WrapPanel Margin="4" DockPanel.Dock="Top">
<ListBox
x:Name="lstGroup"
Margin="{StaticResource MarginLr4}"
Margin="4,0"
DisplayMemberBinding="{Binding Remarks}"
ItemsSource="{Binding SubItems}"
Theme="{DynamicResource ButtonRadioGroupListBox}">
ItemsSource="{Binding SubItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
@ -90,11 +89,20 @@
Binding="{Binding ConfigType}"
Header="{x:Static resx:ResUI.LvServiceType}"
Tag="ConfigType" />
<DataGridTextColumn
Width="120"
Binding="{Binding Remarks}"
Header="{x:Static resx:ResUI.LvRemarks}"
Tag="Remarks" />
<DataGridTemplateColumn SortMemberPath="Remarks" Tag="Remarks">
<DataGridTemplateColumn.Header>
<TextBlock Text="{x:Static resx:ResUI.LvRemarks}" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="8,0" Orientation="Horizontal">
<TextBlock Text="{Binding Remarks}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="120"
Binding="{Binding Address}"

View file

@ -1,12 +1,17 @@
using System.Linq;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
using Avalonia.ReactiveUI;
using ReactiveUI;
using ServiceLib.Manager;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views;

View file

@ -99,14 +99,13 @@
<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.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}" />
<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}" />
<Separator />
<MenuItem x:Name="menuGenGroupMultipleServerSingBoxLeastPing" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" />
<MenuItem x:Name="menuGenGroupMultipleServerSingBoxFallback" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxFallback}" />
<MenuItem x:Name="menuSetDefaultMultipleServerSingBoxLeastPing" Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerSingBoxLeastPing}" />
</MenuItem>
<Separator />
<MenuItem x:Name="menuMixedTestServer" Header="{x:Static resx:ResUI.menuMixedTestServer}" />

View file

@ -69,12 +69,11 @@ 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.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);
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);
//servers move
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
@ -113,6 +112,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
});
RestoreUI();
ViewModel?.RefreshServers();
}
private async void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
@ -171,11 +171,6 @@ 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;

View file

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Interactivity;
using ReactiveUI;
using v2rayN.Desktop.Base;
using System.Threading.Tasks;
namespace v2rayN.Desktop.Views;

View file

@ -3,6 +3,7 @@ using Avalonia;
using Avalonia.Interactivity;
using ReactiveUI;
using v2rayN.Desktop.Base;
using System.Threading.Tasks;
namespace v2rayN.Desktop.Views;
@ -63,7 +64,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, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)
{
@ -78,7 +79,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, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)
{

View file

@ -1,213 +0,0 @@
<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>

View file

@ -1,141 +0,0 @@
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;
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();
}
}
}

View file

@ -281,7 +281,7 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbFakeIPTips}"
Text="{x:Static resx:ResUI.TbApplyProxyDomainsOnly}"
TextWrapping="Wrap" />
<TextBlock

View file

@ -71,14 +71,6 @@
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"

View file

@ -83,8 +83,6 @@ 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);
@ -195,11 +193,6 @@ 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);

View file

@ -3,8 +3,10 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Threading;
using ReactiveUI;
using ServiceLib.Manager;
using Splat;
using v2rayN.Base;
namespace v2rayN.Views;
@ -32,6 +34,7 @@ public partial class ProfilesSelectWindow
ViewModel = new ProfilesSelectViewModel(UpdateViewHandler);
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.ProfileItems, v => v.lstProfiles.ItemsSource).DisposeWith(disposables);
@ -41,8 +44,6 @@ public partial class ProfilesSelectWindow
this.Bind(ViewModel, vm => vm.SelectedSub, v => v.lstGroup.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServerFilter, v => v.txtServerFilter.Text).DisposeWith(disposables);
});
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);
}
public void AllowMultiSelect(bool allow)
@ -189,6 +190,5 @@ public partial class ProfilesSelectWindow
// Trigger selection finalize when Confirm is clicked
ViewModel?.SelectFinish();
}
#endregion Event
}

View file

@ -125,32 +125,28 @@
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuShareServer}" />
<Separator />
<MenuItem Header="{x:Static resx:ResUI.menuGenGroupMultipleServer}">
<MenuItem Header="{x:Static resx:ResUI.menuSetDefaultMultipleServer}">
<MenuItem
x:Name="menuGenGroupMultipleServerXrayRandom"
x:Name="menuSetDefaultMultipleServerXrayRandom"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRandom}" />
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayRandom}" />
<MenuItem
x:Name="menuGenGroupMultipleServerXrayRoundRobin"
x:Name="menuSetDefaultMultipleServerXrayRoundRobin"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayRoundRobin}" />
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayRoundRobin}" />
<MenuItem
x:Name="menuGenGroupMultipleServerXrayLeastPing"
x:Name="menuSetDefaultMultipleServerXrayLeastPing"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastPing}" />
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayLeastPing}" />
<MenuItem
x:Name="menuGenGroupMultipleServerXrayLeastLoad"
x:Name="menuSetDefaultMultipleServerXrayLeastLoad"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastLoad}" />
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerXrayLeastLoad}" />
<Separator />
<MenuItem
x:Name="menuGenGroupMultipleServerSingBoxLeastPing"
x:Name="menuSetDefaultMultipleServerSingBoxLeastPing"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" />
<MenuItem
x:Name="menuGenGroupMultipleServerSingBoxFallback"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxFallback}" />
Header="{x:Static resx:ResUI.menuSetDefaultMultipleServerSingBoxLeastPing}" />
</MenuItem>
<Separator />
<MenuItem

View file

@ -63,12 +63,11 @@ 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.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);
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);
//servers move
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
@ -107,6 +106,7 @@ public partial class ProfilesView
});
RestoreUI();
ViewModel?.RefreshServers();
}
#region Event
@ -119,7 +119,7 @@ public partial class ProfilesView
if (obj is null)
return false;
WindowsUtils.SetClipboardData((string)obj);
break;
break;
case EViewAction.ProfilesFocus:
lstProfiles.Focus();
@ -152,11 +152,6 @@ 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;

View file

@ -1,5 +1,6 @@
using System.Reactive.Disposables;
using System.Windows;
using System.Windows.Threading;
using ReactiveUI;
using ServiceLib.Manager;

View file

@ -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, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, 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, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true);
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
if (selectWindow.ShowDialog() == true)
{
var profile = await selectWindow.ProfileItem;