Group preview (#8760)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
release Linux / rpm (push) Has been cancelled

* Group Preview

* Fix
This commit is contained in:
DHR60 2026-02-06 06:33:58 +00:00 committed by GitHub
parent 54608ab2b9
commit 0f3a3eac02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 213 additions and 21 deletions

View file

@ -75,7 +75,7 @@ public class GroupProfileManager
return (await GetChildProfileItemsByProtocolExtra(protocolExtra), protocolExtra);
}
private static async Task<List<ProfileItem>> GetChildProfileItemsByProtocolExtra(ProtocolExtraItem? protocolExtra)
public static async Task<List<ProfileItem>> GetChildProfileItemsByProtocolExtra(ProtocolExtraItem? protocolExtra)
{
if (protocolExtra == null)
{

View file

@ -1680,6 +1680,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Configuration item preview 的本地化字符串。
/// </summary>
public static string menuServerListPreview {
get {
return ResourceManager.GetString("menuServerListPreview", resourceCulture);
}
}
/// <summary>
/// 查找类似 Configuration 的本地化字符串。
/// </summary>

View file

@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
</root>

View file

@ -1665,4 +1665,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
</root>

View file

@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
</root>

View file

@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
</root>

View file

@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
</root>

View file

@ -1665,4 +1665,7 @@
<data name="TbHopInt7" xml:space="preserve">
<value>端口跳跃间隔</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>子配置项预览</value>
</data>
</root>

View file

@ -1665,4 +1665,7 @@
<data name="TbHopInt7" xml:space="preserve">
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value>
</data>
</root>

View file

@ -27,6 +27,8 @@ public class AddGroupServerViewModel : MyReactiveObject
public IObservableCollection<ProfileItem> ChildItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
public IObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
//public ReactiveCommand<Unit, Unit> AddCmd { get; }
public ReactiveCommand<Unit, Unit> RemoveCmd { get; }
@ -182,6 +184,32 @@ public class AddGroupServerViewModel : MyReactiveObject
await Task.CompletedTask;
}
private ProtocolExtraItem GetUpdatedProtocolExtra()
{
return SelectedSource.GetProtocolExtra() with
{
ChildItems =
Utils.List2String(ChildItemsObs.Where(s => !s.IndexId.IsNullOrEmpty()).Select(s => s.IndexId).ToList()),
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,
},
SubChildItems = SelectedSubItem?.Id,
Filter = Filter,
};
}
public async Task UpdatePreviewList()
{
AllProfilePreviewItemsObs.Clear();
AllProfilePreviewItemsObs.AddRange(await GroupProfileManager.GetChildProfileItemsByProtocolExtra(GetUpdatedProtocolExtra()));
}
private async Task SaveServerAsync()
{
var remarks = SelectedSource.Remarks;
@ -202,24 +230,11 @@ public class AddGroupServerViewModel : MyReactiveObject
return;
}
SelectedSource.SetProtocolExtra(SelectedSource.GetProtocolExtra() with
{
ChildItems =
Utils.List2String(ChildItemsObs.Where(s => !s.IndexId.IsNullOrEmpty()).Select(s => s.IndexId).ToList()),
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,
},
SubChildItems = SelectedSubItem?.Id,
Filter = Filter,
});
var protocolExtra = GetUpdatedProtocolExtra();
var hasCycle = await GroupProfileManager.HasCycle(SelectedSource.IndexId, SelectedSource.GetProtocolExtra());
SelectedSource.SetProtocolExtra(protocolExtra);
var hasCycle = await GroupProfileManager.HasCycle(SelectedSource.IndexId, protocolExtra);
if (hasCycle)
{
NoticeManager.Instance.Enqueue(string.Format(ResUI.GroupSelfReference, remarks));

View file

@ -88,7 +88,10 @@
</Grid>
</Grid>
<TabControl HorizontalContentAlignment="Stretch" DockPanel.Dock="Top">
<TabControl
x:Name="tabControl"
HorizontalContentAlignment="Stretch"
DockPanel.Dock="Top">
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList}">
<Grid
Margin="{StaticResource Margin8}"
@ -134,7 +137,6 @@
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerList2}">
<DataGrid
x:Name="lstChild"
Grid.Row="1"
AutoGenerateColumns="False"
Background="Transparent"
BorderThickness="1"
@ -204,6 +206,48 @@
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerListPreview}">
<DataGrid
x:Name="lstPreviewChild"
AutoGenerateColumns="False"
Background="Transparent"
BorderThickness="1"
CanUserReorderColumns="False"
CanUserResizeColumns="True"
CanUserSortColumns="False"
GridLinesVisibility="All"
HeadersVisibility="Column"
IsReadOnly="True"
ItemsSource="{Binding AllProfilePreviewItemsObs}"
SelectionMode="Extended">
<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

@ -16,6 +16,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
Loaded += Window_Loaded;
btnCancel.Click += (s, e) => Close();
lstChild.SelectionChanged += LstChild_SelectionChanged;
tabControl.SelectionChanged += TabControl_SelectionChanged;
ViewModel = new AddGroupServerViewModel(profileItem, UpdateViewHandler);
@ -38,6 +39,10 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
case EConfigType.ProxyChain:
Title = ResUI.TbConfigTypeProxyChain;
gridPolicyGroup.IsVisible = false;
if (tabControl.Items.Count > 0)
{
tabControl.Items.RemoveAt(0);
}
break;
}
@ -50,7 +55,6 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Filter, v => v.txtFilter.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);
@ -167,4 +171,29 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
ViewModel.SelectedChildren = lstChild.SelectedItems.Cast<ProfileItem>().ToList();
}
}
private async void TabControl_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
try
{
if (e.Source is not TabControl tc)
{
return;
}
if (!(tc.SelectedIndex == tc.Items.Count - 1 && tc.Items.Count > 0))
{
return;
}
if (ViewModel == null)
{
return;
}
await ViewModel.UpdatePreviewList();
}
catch
{
// ignored
}
}
}

View file

@ -135,6 +135,7 @@
</Grid>
<TabControl
x:Name="tabControl"
Margin="{StaticResource Margin8}"
HorizontalContentAlignment="Left"
DockPanel.Dock="Top">
@ -272,6 +273,47 @@
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem HorizontalAlignment="Left" Header="{x:Static resx:ResUI.menuServerListPreview}">
<DataGrid
x:Name="lstPreviewChild"
AutoGenerateColumns="False"
BorderThickness="1"
CanUserAddRows="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
EnableRowVirtualization="True"
GridLinesVisibility="All"
HeadersVisibility="Column"
IsReadOnly="True"
Style="{StaticResource DefDataGrid}">
<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

@ -11,6 +11,7 @@ public partial class AddGroupServerWindow
PreviewKeyDown += AddGroupServerWindow_PreviewKeyDown;
lstChild.SelectionChanged += LstChild_SelectionChanged;
menuSelectAllChild.Click += MenuSelectAllChild_Click;
tabControl.SelectionChanged += TabControl_SelectionChanged;
ViewModel = new AddGroupServerViewModel(profileItem, UpdateViewHandler);
@ -33,6 +34,10 @@ public partial class AddGroupServerWindow
case EConfigType.ProxyChain:
Title = ResUI.TbConfigTypeProxyChain;
gridPolicyGroup.Visibility = Visibility.Collapsed;
if (tabControl.Items.Count > 0)
{
tabControl.Items.RemoveAt(0);
}
break;
}
@ -48,6 +53,8 @@ public partial class AddGroupServerWindow
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.OneWayBind(ViewModel, vm => vm.AllProfilePreviewItemsObs, v => v.lstPreviewChild.ItemsSource).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);
@ -148,4 +155,29 @@ public partial class AddGroupServerWindow
{
lstChild.SelectAll();
}
private async void TabControl_SelectionChanged(object? sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
try
{
if (e.Source is not System.Windows.Controls.TabControl tc)
{
return;
}
if (!(tc.SelectedIndex == tc.Items.Count - 1 && tc.Items.Count > 0))
{
return;
}
if (ViewModel == null)
{
return;
}
await ViewModel.UpdatePreviewList();
}
catch
{
// ignored
}
}
}