|
@@ -1,6 +1,8 @@
|
|
|
using System.Globalization;
|
|
using System.Globalization;
|
|
|
using System.Windows;
|
|
using System.Windows;
|
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls;
|
|
|
|
|
+using System.Windows.Media;
|
|
|
|
|
+using System.Windows.Media.Animation;
|
|
|
using QuickIP.Client.Models;
|
|
using QuickIP.Client.Models;
|
|
|
using QuickIP.Client.Services;
|
|
using QuickIP.Client.Services;
|
|
|
|
|
|
|
@@ -14,6 +16,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
private readonly string _password;
|
|
private readonly string _password;
|
|
|
private bool _configValidated;
|
|
private bool _configValidated;
|
|
|
private bool _suppressConfigChangeHandling;
|
|
private bool _suppressConfigChangeHandling;
|
|
|
|
|
+ private CancellationTokenSource? _statusMessageCts;
|
|
|
|
|
|
|
|
public DeviceDetailsWindow(string baseAddress, string localIPv4, string password)
|
|
public DeviceDetailsWindow(string baseAddress, string localIPv4, string password)
|
|
|
{
|
|
{
|
|
@@ -76,7 +79,6 @@ public partial class DeviceDetailsWindow : Window
|
|
|
NewMaskTextBox.Text = string.Empty;
|
|
NewMaskTextBox.Text = string.Empty;
|
|
|
NewGatewayTextBox.Text = string.Empty;
|
|
NewGatewayTextBox.Text = string.Empty;
|
|
|
NewDnsTextBox.Text = string.Empty;
|
|
NewDnsTextBox.Text = string.Empty;
|
|
|
- ConfigValidationTextBlock.Text = "点击 1/2/3 按顺序操作:先读取当前配置,再校验,最后应用。";
|
|
|
|
|
_configValidated = false;
|
|
_configValidated = false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -99,7 +101,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
RemoteConfigIpTextBlock.Text = "读取失败";
|
|
RemoteConfigIpTextBlock.Text = "读取失败";
|
|
|
RemoteConfigGatewayTextBlock.Text = "读取失败";
|
|
RemoteConfigGatewayTextBlock.Text = "读取失败";
|
|
|
RemoteConfigDnsTextBlock.Text = "读取失败";
|
|
RemoteConfigDnsTextBlock.Text = "读取失败";
|
|
|
- ConfigValidationTextBlock.Text = $"读取目标接口 {interfaceName} 配置失败:{result.Message}";
|
|
|
|
|
|
|
+ ShowStatusMessage($"读取目标接口 {interfaceName} 配置失败:{result.Message}");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -115,7 +117,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
NewDnsTextBox.Text = config.Dns.FirstOrDefault() ?? string.Empty;
|
|
NewDnsTextBox.Text = config.Dns.FirstOrDefault() ?? string.Empty;
|
|
|
_suppressConfigChangeHandling = false;
|
|
_suppressConfigChangeHandling = false;
|
|
|
_configValidated = false;
|
|
_configValidated = false;
|
|
|
- ConfigValidationTextBlock.Text = "已回填目标接口当前配置,可直接修改后校验。";
|
|
|
|
|
|
|
+ ShowStatusMessage("已回填目标接口当前配置,可直接修改后校验。");
|
|
|
UpdateButtonStates();
|
|
UpdateButtonStates();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -146,14 +148,14 @@ public partial class DeviceDetailsWindow : Window
|
|
|
{
|
|
{
|
|
|
var warnings = result.Data.Warnings.Count > 0 ? $" 警告:{string.Join(";", result.Data.Warnings)}" : string.Empty;
|
|
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;
|
|
var errors = result.Data.Errors.Count > 0 ? $" 错误:{string.Join(";", result.Data.Errors)}" : string.Empty;
|
|
|
- ConfigValidationTextBlock.Text = result.Success ? $"校验通过。{warnings}" : $"校验失败。{errors}{warnings}";
|
|
|
|
|
|
|
+ ShowStatusMessage(result.Success ? $"校验通过。{warnings}" : $"校验失败。{errors}{warnings}");
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
- ConfigValidationTextBlock.Text = $"校验失败:{result.Message}";
|
|
|
|
|
|
|
+ ShowStatusMessage($"校验失败:{result.Message}");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- ConfigValidationTextBlock.Text = _configValidated ? "配置已通过校验,可提交应用。" : "当前配置尚未通过校验。";
|
|
|
|
|
|
|
+ ShowStatusMessage(_configValidated ? "配置已通过校验,可提交应用。" : "当前配置尚未通过校验。");
|
|
|
UpdateButtonStates();
|
|
UpdateButtonStates();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -183,11 +185,11 @@ public partial class DeviceDetailsWindow : Window
|
|
|
var applyResult = await _agentApiService.ApplyInterfaceConfigAsync(_baseAddress, _password, _localIPv4, request);
|
|
var applyResult = await _agentApiService.ApplyInterfaceConfigAsync(_baseAddress, _password, _localIPv4, request);
|
|
|
if (!applyResult.Success || applyResult.Data is null)
|
|
if (!applyResult.Success || applyResult.Data is null)
|
|
|
{
|
|
{
|
|
|
- ConfigValidationTextBlock.Text = $"提交配置任务失败:{applyResult.Message}";
|
|
|
|
|
|
|
+ ShowStatusMessage($"提交配置任务失败:{applyResult.Message}");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- ConfigValidationTextBlock.Text = $"配置任务已提交:{applyResult.Data.TaskId},正在轮询状态。";
|
|
|
|
|
|
|
+ ShowStatusMessage($"配置任务已提交:{applyResult.Data.TaskId},正在轮询状态。");
|
|
|
await PollTaskAsync(applyResult.Data.TaskId);
|
|
await PollTaskAsync(applyResult.Data.TaskId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -203,17 +205,17 @@ public partial class DeviceDetailsWindow : Window
|
|
|
if (result.StatusCode is null)
|
|
if (result.StatusCode is null)
|
|
|
{
|
|
{
|
|
|
transientFailureCount++;
|
|
transientFailureCount++;
|
|
|
- ConfigValidationTextBlock.Text = $"任务 {taskId} 轮询中,检测到短暂断连,正在重试({transientFailureCount})。";
|
|
|
|
|
|
|
+ ShowStatusMessage($"任务 {taskId} 轮询中,检测到短暂断连,正在重试({transientFailureCount})。");
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- ConfigValidationTextBlock.Text = $"读取任务状态失败:{result.Message}";
|
|
|
|
|
|
|
+ ShowStatusMessage($"读取任务状态失败:{result.Message}");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
transientFailureCount = 0;
|
|
transientFailureCount = 0;
|
|
|
var task = result.Data;
|
|
var task = result.Data;
|
|
|
- ConfigValidationTextBlock.Text = $"任务 {task.TaskId} / {task.Status} / {task.Step} / {task.Detail}";
|
|
|
|
|
|
|
+ ShowStatusMessage($"任务 {task.TaskId} / {task.Status} / {task.Step} / {task.Detail}");
|
|
|
if (task.Status is "success" or "failed" or "rolled_back")
|
|
if (task.Status is "success" or "failed" or "rolled_back")
|
|
|
{
|
|
{
|
|
|
if (RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo selected)
|
|
if (RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo selected)
|
|
@@ -225,7 +227,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- ConfigValidationTextBlock.Text = $"任务 {taskId} 轮询超时,请稍后手动刷新。";
|
|
|
|
|
|
|
+ ShowStatusMessage($"任务 {taskId} 轮询超时,请稍后手动刷新。");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private async void RebootButton_OnClick(object sender, RoutedEventArgs e)
|
|
private async void RebootButton_OnClick(object sender, RoutedEventArgs e)
|
|
@@ -254,24 +256,24 @@ public partial class DeviceDetailsWindow : Window
|
|
|
var result = await action();
|
|
var result = await action();
|
|
|
if (!result.Success || result.Data is null)
|
|
if (!result.Success || result.Data is null)
|
|
|
{
|
|
{
|
|
|
- ConfigValidationTextBlock.Text = $"{title}失败:{result.Message}";
|
|
|
|
|
|
|
+ ShowStatusMessage($"{title}失败:{result.Message}");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- ConfigValidationTextBlock.Text = $"{title}任务已提交:{result.Data.TaskId}。命令已发出,设备可能立即断开。";
|
|
|
|
|
|
|
+ ShowStatusMessage($"{title}任务已提交:{result.Data.TaskId}。命令已发出,设备可能立即断开。");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private RemoteInterfaceConfig? BuildConfigRequest(string interfaceName)
|
|
private RemoteInterfaceConfig? BuildConfigRequest(string interfaceName)
|
|
|
{
|
|
{
|
|
|
if (string.IsNullOrWhiteSpace(NewIpTextBox.Text))
|
|
if (string.IsNullOrWhiteSpace(NewIpTextBox.Text))
|
|
|
{
|
|
{
|
|
|
- ConfigValidationTextBlock.Text = "IP 地址不能为空。";
|
|
|
|
|
|
|
+ ShowStatusMessage("IP 地址不能为空。");
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (!TryMaskToPrefix(NewMaskTextBox.Text, out var prefix))
|
|
if (!TryMaskToPrefix(NewMaskTextBox.Text, out var prefix))
|
|
|
{
|
|
{
|
|
|
- ConfigValidationTextBlock.Text = "子网掩码格式不正确。";
|
|
|
|
|
|
|
+ ShowStatusMessage("子网掩码格式不正确。");
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -352,10 +354,77 @@ public partial class DeviceDetailsWindow : Window
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_configValidated = false;
|
|
_configValidated = false;
|
|
|
- ConfigValidationTextBlock.Text = "配置内容已变更,请重新点击“2. 校验配置”。";
|
|
|
|
|
|
|
+ ShowStatusMessage("配置内容已变更,请重新点击“2. 校验配置”。");
|
|
|
UpdateButtonStates();
|
|
UpdateButtonStates();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private void ShowStatusMessage(string message)
|
|
|
|
|
+ {
|
|
|
|
|
+ ApplyStatusMessageStyle(message);
|
|
|
|
|
+ StatusMessageTextBlock.Text = message;
|
|
|
|
|
+ StatusMessageBorder.Opacity = 0;
|
|
|
|
|
+ StatusMessageBorder.Visibility = Visibility.Visible;
|
|
|
|
|
+ StatusMessageBorder.BeginAnimation(OpacityProperty, new DoubleAnimation(1, TimeSpan.FromMilliseconds(160)));
|
|
|
|
|
+ _statusMessageCts?.Cancel();
|
|
|
|
|
+ _statusMessageCts = new CancellationTokenSource();
|
|
|
|
|
+ _ = HideStatusMessageAsync(_statusMessageCts.Token);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async Task HideStatusMessageAsync(CancellationToken cancellationToken)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ await Task.Delay(3000, cancellationToken);
|
|
|
|
|
+ await Dispatcher.InvokeAsync(() =>
|
|
|
|
|
+ {
|
|
|
|
|
+ var animation = new DoubleAnimation(0, TimeSpan.FromMilliseconds(200));
|
|
|
|
|
+ animation.Completed += (_, _) =>
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!cancellationToken.IsCancellationRequested)
|
|
|
|
|
+ {
|
|
|
|
|
+ StatusMessageBorder.Visibility = Visibility.Collapsed;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ StatusMessageBorder.BeginAnimation(OpacityProperty, animation);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (TaskCanceledException)
|
|
|
|
|
+ {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void ApplyStatusMessageStyle(string message)
|
|
|
|
|
+ {
|
|
|
|
|
+ var (background, foreground) = GetStatusMessageBrushes(message);
|
|
|
|
|
+ StatusMessageBorder.Background = background;
|
|
|
|
|
+ StatusMessageTextBlock.Foreground = foreground;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static (Brush Background, Brush Foreground) GetStatusMessageBrushes(string message)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (ContainsAny(message, "失败", "错误", "拒绝", "超时", "不能为空", "不正确", "无法"))
|
|
|
|
|
+ {
|
|
|
|
|
+ return (new SolidColorBrush((Color)ColorConverter.ConvertFromString("#B91C1C")), Brushes.White);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (ContainsAny(message, "未发现", "请", "重试", "警告", "需要"))
|
|
|
|
|
+ {
|
|
|
|
|
+ return (new SolidColorBrush((Color)ColorConverter.ConvertFromString("#C2410C")), Brushes.White);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (ContainsAny(message, "成功", "已切换", "已刷新", "已读取", "已加载", "已发现", "已提交", "已回填"))
|
|
|
|
|
+ {
|
|
|
|
|
+ return (new SolidColorBrush((Color)ColorConverter.ConvertFromString("#047857")), Brushes.White);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (new SolidColorBrush((Color)ColorConverter.ConvertFromString("#111827")), Brushes.White);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static bool ContainsAny(string message, params string[] markers)
|
|
|
|
|
+ {
|
|
|
|
|
+ return markers.Any(marker => message.Contains(marker, StringComparison.Ordinal));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private void UpdateButtonStates()
|
|
private void UpdateButtonStates()
|
|
|
{
|
|
{
|
|
|
ReloadInterfaceConfigButton.IsEnabled = RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo;
|
|
ReloadInterfaceConfigButton.IsEnabled = RemoteTargetInterfaceComboBox.SelectedItem is RemoteInterfaceInfo;
|