This commit is contained in:
DHR60 2025-11-02 12:38:21 +08:00
parent c14a0e33b7
commit 640edb5eb2
13 changed files with 176 additions and 77 deletions

View file

@ -87,6 +87,24 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Certificate not set 的本地化字符串。
/// </summary>
public static string CertNotSet {
get {
return ResourceManager.GetString("CertNotSet", resourceCulture);
}
}
/// <summary>
/// 查找类似 Certificate set 的本地化字符串。
/// </summary>
public static string CertSet {
get {
return ResourceManager.GetString("CertSet", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Please check the Configuration settings first. 的本地化字符串。 /// 查找类似 Please check the Configuration settings first. 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1618,4 +1618,10 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="ServerNameMustBeValidDomain" xml:space="preserve"> <data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value> <value>Please set a valid domain</value>
</data> </data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root> </root>

View file

@ -1615,4 +1615,10 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="ServerNameMustBeValidDomain" xml:space="preserve"> <data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value> <value>Please set a valid domain</value>
</data> </data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root> </root>

View file

@ -1618,4 +1618,10 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="ServerNameMustBeValidDomain" xml:space="preserve"> <data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value> <value>Please set a valid domain</value>
</data> </data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root> </root>

View file

@ -1618,4 +1618,10 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="ServerNameMustBeValidDomain" xml:space="preserve"> <data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value> <value>Please set a valid domain</value>
</data> </data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root> </root>

View file

@ -1618,4 +1618,10 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="ServerNameMustBeValidDomain" xml:space="preserve"> <data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value> <value>Please set a valid domain</value>
</data> </data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root> </root>

View file

@ -1615,4 +1615,10 @@
<data name="ServerNameMustBeValidDomain" xml:space="preserve"> <data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>请设置有效的域名</value> <value>请设置有效的域名</value>
</data> </data>
<data name="CertNotSet" xml:space="preserve">
<value>证书未设置</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>证书已设置</value>
</data>
</root> </root>

View file

@ -1615,4 +1615,10 @@ Do not use the "Fetch Certificate" button when "Allow Insecure" is enabled.</val
<data name="ServerNameMustBeValidDomain" xml:space="preserve"> <data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Please set a valid domain</value> <value>Please set a valid domain</value>
</data> </data>
<data name="CertNotSet" xml:space="preserve">
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Certificate set</value>
</data>
</root> </root>

View file

@ -2,6 +2,8 @@ namespace ServiceLib.ViewModels;
public class AddServerViewModel : MyReactiveObject public class AddServerViewModel : MyReactiveObject
{ {
private string _certError = string.Empty;
[Reactive] [Reactive]
public ProfileItem SelectedSource { get; set; } public ProfileItem SelectedSource { get; set; }
@ -11,6 +13,9 @@ public class AddServerViewModel : MyReactiveObject
[Reactive] [Reactive]
public string Cert { get; set; } public string Cert { get; set; }
[Reactive]
public string CertTip { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; } public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; } public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; } public ReactiveCommand<Unit, Unit> SaveCmd { get; }
@ -33,6 +38,9 @@ public class AddServerViewModel : MyReactiveObject
await SaveServerAsync(); await SaveServerAsync();
}); });
this.WhenAnyValue(x => x.Cert)
.Subscribe(_ => UpdateCertTip());
if (profileItem.IndexId.IsNullOrEmpty()) if (profileItem.IndexId.IsNullOrEmpty())
{ {
profileItem.Network = Global.DefaultNetwork; profileItem.Network = Global.DefaultNetwork;
@ -104,6 +112,13 @@ public class AddServerViewModel : MyReactiveObject
} }
} }
private void UpdateCertTip()
{
CertTip = _certError.IsNullOrEmpty()
? (Cert.IsNullOrEmpty() ? ResUI.CertNotSet : ResUI.CertSet)
: _certError;
}
private async Task FetchCert() private async Task FetchCert()
{ {
if (SelectedSource.StreamSecurity != Global.StreamSecurity) if (SelectedSource.StreamSecurity != Global.StreamSecurity)
@ -122,7 +137,9 @@ public class AddServerViewModel : MyReactiveObject
} }
if (!Utils.IsDomain(serverName)) if (!Utils.IsDomain(serverName))
{ {
NoticeManager.Instance.Enqueue(ResUI.ServerNameMustBeValidDomain); _certError = ResUI.ServerNameMustBeValidDomain;
UpdateCertTip();
_certError = string.Empty;
return; return;
} }
if (SelectedSource.Port > 0) if (SelectedSource.Port > 0)
@ -150,7 +167,9 @@ public class AddServerViewModel : MyReactiveObject
} }
if (!Utils.IsDomain(serverName)) if (!Utils.IsDomain(serverName))
{ {
NoticeManager.Instance.Enqueue(ResUI.ServerNameMustBeValidDomain); _certError = ResUI.ServerNameMustBeValidDomain;
UpdateCertTip();
_certError = string.Empty;
return; return;
} }
if (SelectedSource.Port > 0) if (SelectedSource.Port > 0)

View file

@ -775,50 +775,59 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbCertPinning}" /> Text="{x:Static resx:ResUI.TbCertPinning}" />
<Button <StackPanel
Grid.Row="5" Grid.Row="5"
Grid.Column="1" Grid.Column="1"
Margin="{StaticResource MarginLr8}" VerticalAlignment="Center"
HorizontalAlignment="Left" Orientation="Horizontal">
Classes="IconButton"> <TextBlock
<Button.Content> x:Name="labCertPinning"
<PathIcon Data="{StaticResource SemiIconMore}"> Margin="{StaticResource Margin8}"
<PathIcon.RenderTransform> VerticalAlignment="Center" />
<RotateTransform Angle="90" /> <Button
</PathIcon.RenderTransform> Margin="{StaticResource MarginLr4}"
</PathIcon> HorizontalAlignment="Left"
</Button.Content> VerticalAlignment="Center"
<Button.Flyout> Classes="IconButton">
<Flyout> <Button.Content>
<StackPanel> <PathIcon Data="{StaticResource SemiIconMore}">
<TextBlock <PathIcon.RenderTransform>
Margin="{StaticResource Margin4}" <RotateTransform Angle="90" />
VerticalAlignment="Center" </PathIcon.RenderTransform>
Text="{x:Static resx:ResUI.TbCertPinningTips}" /> </PathIcon>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal"> </Button.Content>
<Button <Button.Flyout>
x:Name="btnFetchCert" <Flyout>
<StackPanel>
<TextBlock
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCert}" /> VerticalAlignment="Center"
<Button Text="{x:Static resx:ResUI.TbCertPinningTips}" />
x:Name="btnFetchCertChain" <StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<Button
x:Name="btnFetchCert"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCert}" />
<Button
x:Name="btnFetchCertChain"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}" />
</StackPanel>
<TextBox
x:Name="txtCert"
Width="400"
MinHeight="100"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}" /> HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Classes="TextArea"
MinLines="6"
TextWrapping="Wrap" />
</StackPanel> </StackPanel>
<TextBox </Flyout>
x:Name="txtCert" </Button.Flyout>
Width="400" </Button>
MinHeight="100" </StackPanel>
Margin="{StaticResource Margin4}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Classes="TextArea"
MinLines="6"
TextWrapping="Wrap" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</Grid> </Grid>
<Grid <Grid
x:Name="gridRealityMore" x:Name="gridRealityMore"

View file

@ -185,6 +185,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
//reality //reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);

View file

@ -1004,45 +1004,54 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbCertPinning}" /> Text="{x:Static resx:ResUI.TbCertPinning}" />
<StackPanel
<materialDesign:PopupBox
Grid.Row="5" Grid.Row="5"
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Left" VerticalAlignment="Center"
StaysOpen="True" Orientation="Horizontal">
Style="{StaticResource MaterialDesignToolForegroundPopupBox}"> <TextBlock
<StackPanel> x:Name="labCertPinning"
<TextBlock Margin="{StaticResource Margin4}"
Margin="{StaticResource Margin4}" VerticalAlignment="Center"
VerticalAlignment="Center" Style="{StaticResource ToolbarTextBlock}" />
Style="{StaticResource ToolbarTextBlock}" <materialDesign:PopupBox
Text="{x:Static resx:ResUI.TbCertPinningTips}" HorizontalAlignment="Left"
TextWrapping="Wrap" /> VerticalAlignment="Center"
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal"> StaysOpen="True"
<Button Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
x:Name="btnFetchCert" <StackPanel>
Width="100" <TextBlock
Margin="{StaticResource MarginLeftRight4}" Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbFetchCert}" VerticalAlignment="Center"
Style="{StaticResource DefButton}" /> Style="{StaticResource ToolbarTextBlock}"
<Button Text="{x:Static resx:ResUI.TbCertPinningTips}"
x:Name="btnFetchCertChain" TextWrapping="Wrap" />
Width="100" <StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
Margin="{StaticResource MarginLeftRight4}" <Button
Content="{x:Static resx:ResUI.TbFetchCertChain}" x:Name="btnFetchCert"
Style="{StaticResource DefButton}" /> Width="100"
Margin="{StaticResource MarginLeftRight4}"
Content="{x:Static resx:ResUI.TbFetchCert}"
Style="{StaticResource DefButton}" />
<Button
x:Name="btnFetchCertChain"
Width="100"
Margin="{StaticResource MarginLeftRight4}"
Content="{x:Static resx:ResUI.TbFetchCertChain}"
Style="{StaticResource DefButton}" />
</StackPanel>
<TextBox
x:Name="txtCert"
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
AcceptsReturn="True"
MinLines="6"
Style="{StaticResource MyOutlinedTextBox}"
TextWrapping="Wrap" />
</StackPanel> </StackPanel>
<TextBox </materialDesign:PopupBox>
x:Name="txtCert" </StackPanel>
Width="400"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
AcceptsReturn="True"
MinLines="6"
Style="{StaticResource MyOutlinedTextBox}"
TextWrapping="Wrap" />
</StackPanel>
</materialDesign:PopupBox>
</Grid> </Grid>
<Grid <Grid

View file

@ -180,6 +180,7 @@ public partial class AddServerWindow
this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.AllowInsecure, v => v.cmbAllowInsecure.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
//reality //reality
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);