|
@@ -16,6 +16,10 @@ public partial class MainWindow : Window
|
|
|
private readonly AgentApiService _agentApiService = new();
|
|
private readonly AgentApiService _agentApiService = new();
|
|
|
private readonly AdminPrivilegeService _adminPrivilegeService = new();
|
|
private readonly AdminPrivilegeService _adminPrivilegeService = new();
|
|
|
private IReadOnlyList<AdapterInfo> _adapters = [];
|
|
private IReadOnlyList<AdapterInfo> _adapters = [];
|
|
|
|
|
+ private string _connectedBaseAddress = string.Empty;
|
|
|
|
|
+ private string _connectedLocalIPv4 = string.Empty;
|
|
|
|
|
+ private bool _configValidated;
|
|
|
|
|
+ private bool _suppressConfigChangeHandling;
|
|
|
private bool _isShowingPassword;
|
|
private bool _isShowingPassword;
|
|
|
private bool _suppressPasswordSync;
|
|
private bool _suppressPasswordSync;
|
|
|
|
|
|
|
@@ -77,6 +81,7 @@ public partial class MainWindow : Window
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
AppendLog("客户端已加载连接页。", true);
|
|
AppendLog("客户端已加载连接页。", true);
|
|
|
|
|
+ ClearRemoteDetails();
|
|
|
UpdateButtonStates();
|
|
UpdateButtonStates();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -85,12 +90,14 @@ public partial class MainWindow : Window
|
|
|
if (AdapterComboBox.SelectedItem is not AdapterInfo adapter)
|
|
if (AdapterComboBox.SelectedItem is not AdapterInfo adapter)
|
|
|
{
|
|
{
|
|
|
UpdateAdapterDetails(null);
|
|
UpdateAdapterDetails(null);
|
|
|
|
|
+ ClearRemoteDetails();
|
|
|
SetStatus("请选择一块有线网卡。", false);
|
|
SetStatus("请选择一块有线网卡。", false);
|
|
|
UpdateButtonStates();
|
|
UpdateButtonStates();
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
UpdateAdapterDetails(adapter);
|
|
UpdateAdapterDetails(adapter);
|
|
|
|
|
+ ClearRemoteDetails();
|
|
|
SetStatus(adapter.HasLink
|
|
SetStatus(adapter.HasLink
|
|
|
? $"已选择 {adapter.RecommendationLabel} 网卡,可切换到维护网络。{adapter.RecommendationReason}"
|
|
? $"已选择 {adapter.RecommendationLabel} 网卡,可切换到维护网络。{adapter.RecommendationReason}"
|
|
|
: "当前网卡未检测到链路,请检查网线连接。", true);
|
|
: "当前网卡未检测到链路,请检查网线连接。", true);
|
|
@@ -164,8 +171,11 @@ public partial class MainWindow : Window
|
|
|
var directResult = await _agentApiService.CheckHealthAsync("http://169.254.100.2:48888", GetCurrentPassword(), adapter.IPv4Address);
|
|
var directResult = await _agentApiService.CheckHealthAsync("http://169.254.100.2:48888", GetCurrentPassword(), adapter.IPv4Address);
|
|
|
if (directResult.Success)
|
|
if (directResult.Success)
|
|
|
{
|
|
{
|
|
|
|
|
+ _connectedBaseAddress = "http://169.254.100.2:48888";
|
|
|
|
|
+ _connectedLocalIPv4 = adapter.IPv4Address;
|
|
|
DiscoveredDeviceTextBlock.Text = $"已直接访问管理口:169.254.100.2 / {adapter.Name}";
|
|
DiscoveredDeviceTextBlock.Text = $"已直接访问管理口:169.254.100.2 / {adapter.Name}";
|
|
|
SetStatus("连接成功,无需切换本机网卡。", true);
|
|
SetStatus("连接成功,无需切换本机网卡。", true);
|
|
|
|
|
+ OpenDeviceDetailsWindow(_connectedBaseAddress, _connectedLocalIPv4);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -199,10 +209,21 @@ public partial class MainWindow : Window
|
|
|
SetStatus("已发现设备,正在验证连接。", true);
|
|
SetStatus("已发现设备,正在验证连接。", true);
|
|
|
|
|
|
|
|
var discoveredResult = await _agentApiService.CheckHealthAsync($"http://{device.Lan2Ip}:48888", GetCurrentPassword(), selectedAdapter?.IPv4Address ?? string.Empty);
|
|
var discoveredResult = await _agentApiService.CheckHealthAsync($"http://{device.Lan2Ip}:48888", GetCurrentPassword(), selectedAdapter?.IPv4Address ?? string.Empty);
|
|
|
- SetStatus(discoveredResult.Success ? "连接成功。" : $"设备已发现,但 HTTP 验证失败:{discoveredResult.Message}", true);
|
|
|
|
|
|
|
+ if (discoveredResult.Success)
|
|
|
|
|
+ {
|
|
|
|
|
+ _connectedBaseAddress = $"http://{device.Lan2Ip}:48888";
|
|
|
|
|
+ _connectedLocalIPv4 = selectedAdapter?.IPv4Address ?? string.Empty;
|
|
|
|
|
+ SetStatus("连接成功。", true);
|
|
|
|
|
+ OpenDeviceDetailsWindow(_connectedBaseAddress, _connectedLocalIPv4);
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ SetStatus($"设备已发现,但 HTTP 验证失败:{discoveredResult.Message}", true);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
catch (Exception ex)
|
|
catch (Exception ex)
|
|
|
{
|
|
{
|
|
|
|
|
+ ClearRemoteDetails();
|
|
|
SetStatus($"连接失败:{ex.Message}", true);
|
|
SetStatus($"连接失败:{ex.Message}", true);
|
|
|
MessageBox.Show(this, ex.Message, "连接失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
MessageBox.Show(this, ex.Message, "连接失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
}
|
|
}
|
|
@@ -233,6 +254,9 @@ public partial class MainWindow : Window
|
|
|
var hasAdapter = adapter is not null;
|
|
var hasAdapter = adapter is not null;
|
|
|
SwitchMaintenanceButton.IsEnabled = hasAdapter;
|
|
SwitchMaintenanceButton.IsEnabled = hasAdapter;
|
|
|
DiscoverConnectButton.IsEnabled = hasAdapter && adapter!.HasLink;
|
|
DiscoverConnectButton.IsEnabled = hasAdapter && adapter!.HasLink;
|
|
|
|
|
+ ReloadInterfaceConfigButton.IsEnabled = RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo;
|
|
|
|
|
+ ValidateConfigButton.IsEnabled = RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo;
|
|
|
|
|
+ ApplyConfigButton.IsEnabled = _configValidated && RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private async Task RefreshAdaptersAsync(string? selectedAdapterId = null)
|
|
private async Task RefreshAdaptersAsync(string? selectedAdapterId = null)
|
|
@@ -263,21 +287,334 @@ public partial class MainWindow : Window
|
|
|
{
|
|
{
|
|
|
if (adapter is null)
|
|
if (adapter is null)
|
|
|
{
|
|
{
|
|
|
- AdapterNameTextBlock.Text = "-";
|
|
|
|
|
- AdapterLinkTextBlock.Text = "-";
|
|
|
|
|
- AdapterIPv4TextBlock.Text = "-";
|
|
|
|
|
- AdapterTypeTextBlock.Text = "-";
|
|
|
|
|
AdapterProbeTextBlock.Text = "-";
|
|
AdapterProbeTextBlock.Text = "-";
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- AdapterNameTextBlock.Text = $"{adapter.Description} / {adapter.RecommendationLabel}";
|
|
|
|
|
- AdapterLinkTextBlock.Text = adapter.HasLink ? "已连接" : "未连接";
|
|
|
|
|
- AdapterIPv4TextBlock.Text = string.IsNullOrWhiteSpace(adapter.IPv4Address) ? "无" : adapter.IPv4Address;
|
|
|
|
|
- AdapterTypeTextBlock.Text = adapter.Type;
|
|
|
|
|
AdapterProbeTextBlock.Text = $"{adapter.ProbeStatus} / {adapter.ProbeReason}";
|
|
AdapterProbeTextBlock.Text = $"{adapter.ProbeStatus} / {adapter.ProbeReason}";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private async Task LoadRemoteDetailsAsync(string baseAddress, string localIPv4)
|
|
|
|
|
+ {
|
|
|
|
|
+ ClearRemoteDetails();
|
|
|
|
|
+ RemoteDetailsBorder.Visibility = Visibility.Visible;
|
|
|
|
|
+ SetStatus("正在读取设备信息。", true);
|
|
|
|
|
+ var device = await _agentApiService.GetDeviceInfoAsync(baseAddress, GetCurrentPassword(), localIPv4);
|
|
|
|
|
+ if (device is not null)
|
|
|
|
|
+ {
|
|
|
|
|
+ RemoteDeviceIdTextBlock.Text = device.DeviceId;
|
|
|
|
|
+ RemoteHostnameTextBlock.Text = device.Hostname;
|
|
|
|
|
+ RemoteOsVersionTextBlock.Text = device.OSVersion;
|
|
|
|
|
+ RemoteAgentVersionTextBlock.Text = device.AgentVersion;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ SetStatus("正在读取 Linux 接口列表。", true);
|
|
|
|
|
+ var interfaces = await _agentApiService.GetInterfacesAsync(baseAddress, GetCurrentPassword(), localIPv4);
|
|
|
|
|
+ if (interfaces is not null)
|
|
|
|
|
+ {
|
|
|
|
|
+ RemoteSummaryTextBlock.Text = $"当前管理接口:{interfaces.ManagementInterface};建议目标接口:{interfaces.SuggestedTargetInterface};{(interfaces.RequiresTargetSelection ? "需要手动选择目标接口。" : "已自动识别建议目标接口。")}";
|
|
|
|
|
+
|
|
|
|
|
+ var suggested = interfaces.Interfaces.FirstOrDefault(item => item.SystemName == interfaces.SuggestedTargetInterface)
|
|
|
|
|
+ ?? interfaces.Interfaces.FirstOrDefault(item => item.IsSuggestedTarget)
|
|
|
|
|
+ ?? interfaces.Interfaces.FirstOrDefault(item => !item.IsManagementInterface);
|
|
|
|
|
+
|
|
|
|
|
+ RemoteTargetInterfaceComboBox.ItemsSource = interfaces.Interfaces;
|
|
|
|
|
+ if (suggested is not null)
|
|
|
|
|
+ {
|
|
|
|
|
+ RemoteTargetInterfaceComboBox.SelectedItem = suggested;
|
|
|
|
|
+ await LoadRemoteInterfaceConfigAsync(suggested.SystemName);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ SetStatus("已加载设备信息和 Linux 接口列表。", true);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ RemoteSummaryTextBlock.Text = "设备已连接,但暂时无法读取 Linux 接口列表。";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void OpenDeviceDetailsWindow(string baseAddress, string localIPv4)
|
|
|
|
|
+ {
|
|
|
|
|
+ var window = new DeviceDetailsWindow(baseAddress, localIPv4, GetCurrentPassword())
|
|
|
|
|
+ {
|
|
|
|
|
+ Owner = this,
|
|
|
|
|
+ };
|
|
|
|
|
+ window.ShowDialog();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void ClearRemoteDetails()
|
|
|
|
|
+ {
|
|
|
|
|
+ RemoteDetailsBorder.Visibility = Visibility.Collapsed;
|
|
|
|
|
+ RemoteDeviceIdTextBlock.Text = "-";
|
|
|
|
|
+ RemoteHostnameTextBlock.Text = "-";
|
|
|
|
|
+ RemoteOsVersionTextBlock.Text = "-";
|
|
|
|
|
+ RemoteAgentVersionTextBlock.Text = "-";
|
|
|
|
|
+ RemoteTargetInterfaceComboBox.ItemsSource = null;
|
|
|
|
|
+ RemoteConfigInterfaceTextBlock.Text = "-";
|
|
|
|
|
+ RemoteConfigIpTextBlock.Text = "-";
|
|
|
|
|
+ RemoteConfigGatewayTextBlock.Text = "-";
|
|
|
|
|
+ RemoteConfigDnsTextBlock.Text = "-";
|
|
|
|
|
+ NewIpTextBox.Text = string.Empty;
|
|
|
|
|
+ NewMaskTextBox.Text = string.Empty;
|
|
|
|
|
+ NewGatewayTextBox.Text = string.Empty;
|
|
|
|
|
+ NewDnsTextBox.Text = string.Empty;
|
|
|
|
|
+ ConfigValidationTextBlock.Text = "读取目标接口当前配置后,可在此修改并校验。";
|
|
|
|
|
+ ApplyTaskStatusTextBlock.Text = "尚未提交配置任务。";
|
|
|
|
|
+ _configValidated = false;
|
|
|
|
|
+ RemoteSummaryTextBlock.Text = "连接成功后,这里会显示 Linux 管理接口和建议目标接口。";
|
|
|
|
|
+ UpdateButtonStates();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async void RemoteTargetInterfaceComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (RemoteTargetInterfaceComboBox.SelectedItem is not RemoteInterfaceInfo selected || string.IsNullOrWhiteSpace(_connectedBaseAddress))
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await LoadRemoteInterfaceConfigAsync(selected.SystemName);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task LoadRemoteInterfaceConfigAsync(string interfaceName)
|
|
|
|
|
+ {
|
|
|
|
|
+ SetStatus($"正在读取目标接口 {interfaceName} 当前配置。", true);
|
|
|
|
|
+ var result = await _agentApiService.GetInterfaceConfigAsync(_connectedBaseAddress, GetCurrentPassword(), _connectedLocalIPv4, interfaceName);
|
|
|
|
|
+ if (!result.Success || result.Data is null)
|
|
|
|
|
+ {
|
|
|
|
|
+ RemoteConfigInterfaceTextBlock.Text = interfaceName;
|
|
|
|
|
+ RemoteConfigIpTextBlock.Text = "读取失败";
|
|
|
|
|
+ RemoteConfigGatewayTextBlock.Text = "读取失败";
|
|
|
|
|
+ RemoteConfigDnsTextBlock.Text = "读取失败";
|
|
|
|
|
+ SetStatus($"读取目标接口 {interfaceName} 配置失败:{result.Message}", true);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var config = result.Data;
|
|
|
|
|
+ RemoteConfigInterfaceTextBlock.Text = config.Interface;
|
|
|
|
|
+ RemoteConfigIpTextBlock.Text = string.IsNullOrWhiteSpace(config.IP) ? "无" : $"{config.IP}/{config.Prefix}";
|
|
|
|
|
+ RemoteConfigGatewayTextBlock.Text = string.IsNullOrWhiteSpace(config.Gateway) ? "无" : config.Gateway;
|
|
|
|
|
+ RemoteConfigDnsTextBlock.Text = config.DnsSummary;
|
|
|
|
|
+ _suppressConfigChangeHandling = true;
|
|
|
|
|
+ NewIpTextBox.Text = config.IP;
|
|
|
|
|
+ NewMaskTextBox.Text = PrefixToMask(config.Prefix);
|
|
|
|
|
+ NewGatewayTextBox.Text = config.Gateway;
|
|
|
|
|
+ NewDnsTextBox.Text = config.Dns.FirstOrDefault() ?? string.Empty;
|
|
|
|
|
+ _suppressConfigChangeHandling = false;
|
|
|
|
|
+ _configValidated = false;
|
|
|
|
|
+ ConfigValidationTextBlock.Text = "已回填目标接口当前配置,可直接修改后校验。";
|
|
|
|
|
+ ApplyTaskStatusTextBlock.Text = "尚未提交配置任务。";
|
|
|
|
|
+ UpdateButtonStates();
|
|
|
|
|
+ SetStatus($"已读取目标接口 {interfaceName} 当前配置。", true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async void ReloadInterfaceConfigButton_OnClick(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (RemoteTargetInterfaceComboBox.SelectedItem is not RemoteInterfaceInfo selected)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await LoadRemoteInterfaceConfigAsync(selected.SystemName);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async void ValidateConfigButton_OnClick(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (RemoteTargetInterfaceComboBox.SelectedItem is not RemoteInterfaceInfo selected)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var request = BuildConfigRequest(selected.SystemName);
|
|
|
|
|
+ if (request is null)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ SetStatus($"正在校验目标接口 {selected.SystemName} 的新配置。", true);
|
|
|
|
|
+ var result = await _agentApiService.ValidateInterfaceConfigAsync(_connectedBaseAddress, GetCurrentPassword(), _connectedLocalIPv4, request);
|
|
|
|
|
+ _configValidated = result.Success && result.Data?.Valid == true;
|
|
|
|
|
+ if (result.Data is not null)
|
|
|
|
|
+ {
|
|
|
|
|
+ var warnings = result.Data.Warnings.Count > 0 ? $" 警告:{string.Join(";", result.Data.Warnings)}" : string.Empty;
|
|
|
|
|
+ var errors = result.Data.Errors.Count > 0 ? $" 错误:{string.Join(";", result.Data.Errors)}" : string.Empty;
|
|
|
|
|
+ ConfigValidationTextBlock.Text = result.Success ? $"校验通过。{warnings}" : $"校验失败。{errors}{warnings}";
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ ConfigValidationTextBlock.Text = $"校验失败:{result.Message}";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ApplyTaskStatusTextBlock.Text = _configValidated ? "配置已通过校验,可提交应用。" : "当前配置尚未通过校验。";
|
|
|
|
|
+ UpdateButtonStates();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async void ApplyConfigButton_OnClick(object sender, RoutedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (RemoteTargetInterfaceComboBox.SelectedItem is not RemoteInterfaceInfo selected)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var request = BuildConfigRequest(selected.SystemName);
|
|
|
|
|
+ if (request is null)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var confirmMessage = $"将要把以下配置应用到接口 {selected.SystemName}:\n\n" +
|
|
|
|
|
+ $"IP:{request.IP}/{request.Prefix}\n" +
|
|
|
|
|
+ $"网关:{(string.IsNullOrWhiteSpace(request.Gateway) ? "无" : request.Gateway)}\n" +
|
|
|
|
|
+ $"DNS:{(request.Dns.Count == 0 ? "无" : string.Join(", ", request.Dns))}\n\n" +
|
|
|
|
|
+ "请确认是否继续。";
|
|
|
|
|
+ if (MessageBox.Show(this, confirmMessage, "确认应用配置", MessageBoxButton.OKCancel, MessageBoxImage.Question) != MessageBoxResult.OK)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ SetStatus($"正在提交目标接口 {selected.SystemName} 的配置任务。", true);
|
|
|
|
|
+ var applyResult = await _agentApiService.ApplyInterfaceConfigAsync(_connectedBaseAddress, GetCurrentPassword(), _connectedLocalIPv4, request);
|
|
|
|
|
+ if (!applyResult.Success || applyResult.Data is null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ApplyTaskStatusTextBlock.Text = $"提交配置任务失败:{applyResult.Message}";
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ApplyTaskStatusTextBlock.Text = $"配置任务已提交:{applyResult.Data.TaskId},正在轮询状态。";
|
|
|
|
|
+ await PollTaskAsync(applyResult.Data.TaskId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task PollTaskAsync(string taskId)
|
|
|
|
|
+ {
|
|
|
|
|
+ for (var i = 0; i < 10; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ await Task.Delay(1000);
|
|
|
|
|
+ var result = await _agentApiService.GetTaskAsync(_connectedBaseAddress, GetCurrentPassword(), _connectedLocalIPv4, taskId);
|
|
|
|
|
+ if (!result.Success || result.Data is null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ApplyTaskStatusTextBlock.Text = $"读取任务状态失败:{result.Message}";
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var task = result.Data;
|
|
|
|
|
+ ApplyTaskStatusTextBlock.Text = $"任务 {task.TaskId} / {task.Status} / {task.Step} / {task.Detail}";
|
|
|
|
|
+ if (task.Status is "success" or "failed" or "rolled_back")
|
|
|
|
|
+ {
|
|
|
|
|
+ if (RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo selected)
|
|
|
|
|
+ {
|
|
|
|
|
+ await LoadRemoteInterfaceConfigAsync(selected.SystemName);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ApplyTaskStatusTextBlock.Text = $"任务 {taskId} 轮询超时,请稍后手动刷新。";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private RemoteInterfaceConfig? BuildConfigRequest(string interfaceName)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.IsNullOrWhiteSpace(NewIpTextBox.Text))
|
|
|
|
|
+ {
|
|
|
|
|
+ ConfigValidationTextBlock.Text = "IP 地址不能为空。";
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!TryMaskToPrefix(NewMaskTextBox.Text, out var prefix))
|
|
|
|
|
+ {
|
|
|
|
|
+ ConfigValidationTextBlock.Text = "子网掩码格式不正确。";
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var dns = string.IsNullOrWhiteSpace(NewDnsTextBox.Text)
|
|
|
|
|
+ ? Array.Empty<string>()
|
|
|
|
|
+ : new[] { NewDnsTextBox.Text.Trim() };
|
|
|
|
|
+
|
|
|
|
|
+ return new RemoteInterfaceConfig
|
|
|
|
|
+ {
|
|
|
|
|
+ Interface = interfaceName,
|
|
|
|
|
+ IP = NewIpTextBox.Text.Trim(),
|
|
|
|
|
+ Prefix = prefix,
|
|
|
|
|
+ Gateway = NewGatewayTextBox.Text.Trim(),
|
|
|
|
|
+ Dns = dns,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static string PrefixToMask(int prefix)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (prefix < 0 || prefix > 32)
|
|
|
|
|
+ {
|
|
|
|
|
+ return string.Empty;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var mask = prefix == 0 ? 0u : uint.MaxValue << (32 - prefix);
|
|
|
|
|
+ return string.Join('.', new[]
|
|
|
|
|
+ {
|
|
|
|
|
+ (mask >> 24) & 255,
|
|
|
|
|
+ (mask >> 16) & 255,
|
|
|
|
|
+ (mask >> 8) & 255,
|
|
|
|
|
+ mask & 255,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static bool TryMaskToPrefix(string maskText, out int prefix)
|
|
|
|
|
+ {
|
|
|
|
|
+ prefix = 0;
|
|
|
|
|
+ if (string.IsNullOrWhiteSpace(maskText))
|
|
|
|
|
+ {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var parts = maskText.Trim().Split('.');
|
|
|
|
|
+ if (parts.Length != 4)
|
|
|
|
|
+ {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ uint mask = 0;
|
|
|
|
|
+ foreach (var part in parts)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!byte.TryParse(part, out var octet))
|
|
|
|
|
+ {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ mask = (mask << 8) | octet;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var seenZero = false;
|
|
|
|
|
+ for (var i = 31; i >= 0; i--)
|
|
|
|
|
+ {
|
|
|
|
|
+ var bit = (mask & (1u << i)) != 0;
|
|
|
|
|
+ if (bit && seenZero)
|
|
|
|
|
+ {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (bit)
|
|
|
|
|
+ {
|
|
|
|
|
+ prefix++;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ seenZero = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void ConfigInputChanged_OnChanged(object sender, TextChangedEventArgs e)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_suppressConfigChangeHandling)
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _configValidated = false;
|
|
|
|
|
+ ConfigValidationTextBlock.Text = "配置内容已变更,请重新点击“校验配置”。";
|
|
|
|
|
+ ApplyTaskStatusTextBlock.Text = "当前配置尚未通过校验。";
|
|
|
|
|
+ UpdateButtonStates();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private void SetStatus(string message, bool addLog)
|
|
private void SetStatus(string message, bool addLog)
|
|
|
{
|
|
{
|
|
|
StatusTextBlock.Text = message;
|
|
StatusTextBlock.Text = message;
|