|
|
@@ -4,6 +4,7 @@ using System.ComponentModel;
|
|
|
using System.Runtime.CompilerServices;
|
|
|
using System.Windows;
|
|
|
using System.Windows.Controls;
|
|
|
+using System.Windows.Documents;
|
|
|
using System.Windows.Input;
|
|
|
using System.Windows.Media;
|
|
|
using System.Windows.Media.Animation;
|
|
|
@@ -168,7 +169,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
|
|
|
private async void ReloadInterfaceConfigButton_OnClick(object sender, RoutedEventArgs e)
|
|
|
{
|
|
|
- if (!ConfirmDiscardPendingChanges("当前配置已修改但尚未应用,重新获取会丢失未应用内容。是否继续重新获取?", "确认重新获取配置"))
|
|
|
+ if (!ConfirmDiscardPendingChanges("当前配置已修改但尚未保存,重新获取会丢失未保存内容。是否继续重新获取?", "确认重新获取配置"))
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
@@ -194,7 +195,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
|
|
|
private void DeviceDetailsWindow_OnClosing(object? sender, CancelEventArgs e)
|
|
|
{
|
|
|
- if (!ConfirmDiscardPendingChanges("当前配置已修改但尚未应用。是否关闭窗口?", "确认关闭窗口"))
|
|
|
+ if (!ConfirmDiscardPendingChanges("当前配置已修改但尚未保存。是否关闭窗口?", "确认关闭窗口"))
|
|
|
{
|
|
|
e.Cancel = true;
|
|
|
}
|
|
|
@@ -227,9 +228,9 @@ public partial class DeviceDetailsWindow : Window
|
|
|
{
|
|
|
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;
|
|
|
- SetConfigStateMessage(_configValidated ? "配置已校验通过,可以应用。" : "配置校验未通过,请修正后重新校验。", !_configValidated);
|
|
|
+ SetConfigStateMessage(_configValidated ? "配置已校验通过,可以保存。" : "配置校验未通过,请修正后重新校验。", !_configValidated);
|
|
|
ShowStatusMessage(
|
|
|
- _configValidated ? $"全部网口校验通过,可应用配置。{warnings}" : $"校验失败。{errors}{warnings}",
|
|
|
+ _configValidated ? $"全部网口校验通过,可保存配置。{warnings}" : $"校验失败。{errors}{warnings}",
|
|
|
_configValidated ? StatusMessageType.Success : StatusMessageType.Error);
|
|
|
}
|
|
|
else
|
|
|
@@ -256,14 +257,14 @@ public partial class DeviceDetailsWindow : Window
|
|
|
|
|
|
var changeSummary = FormatChangeSummary();
|
|
|
var confirmMessage = string.IsNullOrWhiteSpace(changeSummary)
|
|
|
- ? "将要一次性应用以下网口配置:\n\n" + FormatConfigSummary(request) + "\n\n请确认是否继续。"
|
|
|
- : "将要一次性应用以下已修改配置:\n\n" + changeSummary + "\n\n完整目标配置:\n\n" + FormatConfigSummary(request) + "\n\n请确认是否继续。";
|
|
|
- if (MessageBox.Show(this, confirmMessage, "确认应用配置", MessageBoxButton.OKCancel, MessageBoxImage.Question) != MessageBoxResult.OK)
|
|
|
+ ? "将要一次性保存以下网口配置:\n\n" + FormatConfigSummary(request) + "\n\n请确认是否继续。"
|
|
|
+ : "将要一次性保存以下已修改配置:\n\n" + changeSummary + "\n\n完整目标配置:\n\n" + FormatConfigSummary(request) + "\n\n请确认是否继续。";
|
|
|
+ if (MessageBox.Show(this, confirmMessage, "确认保存配置", MessageBoxButton.OKCancel, MessageBoxImage.Question) != MessageBoxResult.OK)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- SetBusyState(true, "正在提交并应用配置,请稍候...");
|
|
|
+ SetBusyState(true, "正在提交并保存配置,请稍候...");
|
|
|
try
|
|
|
{
|
|
|
var applyResult = await _serverApiService.ApplyInterfaceConfigsAsync(_baseAddress, _password, _localIPv4, request);
|
|
|
@@ -273,7 +274,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- ShowStatusMessage("配置任务已提交,正在应用并等待连通确认...", StatusMessageType.Info);
|
|
|
+ ShowStatusMessage("配置任务已提交,正在保存并等待连通确认...", StatusMessageType.Info);
|
|
|
await PollTaskAsync(applyResult.Data.TaskId);
|
|
|
}
|
|
|
finally
|
|
|
@@ -338,14 +339,14 @@ public partial class DeviceDetailsWindow : Window
|
|
|
{
|
|
|
_configValidated = false;
|
|
|
_configDirty = false;
|
|
|
- SetConfigStateMessage("配置已应用,当前显示为设备最新配置。", false);
|
|
|
+ SetConfigStateMessage("配置已保存,当前显示为设备最新配置。", false);
|
|
|
+ foreach (var editor in _interfaces)
|
|
|
+ {
|
|
|
+ await LoadRemoteInterfaceConfigAsync(editor);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
ShowTaskCompletionDialog(task);
|
|
|
- foreach (var editor in _interfaces)
|
|
|
- {
|
|
|
- await LoadRemoteInterfaceConfigAsync(editor);
|
|
|
- }
|
|
|
|
|
|
return;
|
|
|
}
|
|
|
@@ -358,27 +359,41 @@ public partial class DeviceDetailsWindow : Window
|
|
|
{
|
|
|
var remaining = timeoutSeconds;
|
|
|
var result = false;
|
|
|
- var messageTextBlock = new TextBlock
|
|
|
+ var deadline = DateTime.UtcNow.AddSeconds(timeoutSeconds);
|
|
|
+ var remainingTextBlock = new TextBlock
|
|
|
{
|
|
|
- Width = 420,
|
|
|
- TextWrapping = TextWrapping.Wrap,
|
|
|
- FontSize = 13,
|
|
|
- Foreground = Brushes.Black,
|
|
|
+ VerticalAlignment = VerticalAlignment.Center,
|
|
|
+ FontSize = 14,
|
|
|
+ FontWeight = FontWeights.SemiBold,
|
|
|
+ };
|
|
|
+ var progressBar = new ProgressBar
|
|
|
+ {
|
|
|
+ Height = 8,
|
|
|
+ Minimum = 0,
|
|
|
+ Maximum = timeoutSeconds,
|
|
|
+ Value = timeoutSeconds,
|
|
|
+ Foreground = new SolidColorBrush(Color.FromRgb(24, 119, 242)),
|
|
|
+ Background = new SolidColorBrush(Color.FromRgb(229, 231, 235)),
|
|
|
+ BorderBrush = Brushes.Transparent,
|
|
|
+ BorderThickness = new Thickness(0),
|
|
|
};
|
|
|
var confirmButton = new Button
|
|
|
{
|
|
|
- MinWidth = 88,
|
|
|
- MinHeight = 32,
|
|
|
- Margin = new Thickness(0, 0, 10, 0),
|
|
|
- Content = "保留",
|
|
|
+ Width = 100,
|
|
|
+ Height = 34,
|
|
|
+ Margin = new Thickness(0, 0, 12, 0),
|
|
|
+ Content = "保留配置",
|
|
|
+ FontSize = 13,
|
|
|
IsDefault = true,
|
|
|
};
|
|
|
confirmButton.Style = (Style)FindResource("PrimaryButtonStyle");
|
|
|
var cancelButton = new Button
|
|
|
{
|
|
|
- MinWidth = 88,
|
|
|
- MinHeight = 32,
|
|
|
- Content = "回滚",
|
|
|
+ Width = 100,
|
|
|
+ Height = 34,
|
|
|
+ Content = "撤销修改",
|
|
|
+ FontSize = 13,
|
|
|
+ Foreground = new SolidColorBrush(Color.FromRgb(31, 41, 55)),
|
|
|
IsCancel = true,
|
|
|
};
|
|
|
var dialog = new Window
|
|
|
@@ -388,41 +403,62 @@ public partial class DeviceDetailsWindow : Window
|
|
|
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
|
|
ResizeMode = ResizeMode.NoResize,
|
|
|
SizeToContent = SizeToContent.WidthAndHeight,
|
|
|
- Content = new StackPanel
|
|
|
+ Content = new Border
|
|
|
{
|
|
|
- Margin = new Thickness(18),
|
|
|
- Children =
|
|
|
+ Width = 460,
|
|
|
+ Padding = new Thickness(16),
|
|
|
+ Background = Brushes.White,
|
|
|
+ Child = new Grid
|
|
|
{
|
|
|
- messageTextBlock,
|
|
|
- new StackPanel
|
|
|
+ RowDefinitions =
|
|
|
{
|
|
|
- Margin = new Thickness(0, 18, 0, 0),
|
|
|
- HorizontalAlignment = HorizontalAlignment.Right,
|
|
|
- Orientation = Orientation.Horizontal,
|
|
|
- Children = { confirmButton, cancelButton },
|
|
|
+ new RowDefinition { Height = GridLength.Auto },
|
|
|
+ new RowDefinition { Height = GridLength.Auto },
|
|
|
+ new RowDefinition { Height = GridLength.Auto },
|
|
|
+ },
|
|
|
+ Children =
|
|
|
+ {
|
|
|
+ CreateApplyConfirmNoticeCard(),
|
|
|
+ CreateApplyConfirmCountdownCard(remainingTextBlock, progressBar),
|
|
|
+ new StackPanel
|
|
|
+ {
|
|
|
+ Margin = new Thickness(0, 24, 0, 0),
|
|
|
+ HorizontalAlignment = HorizontalAlignment.Right,
|
|
|
+ Orientation = Orientation.Horizontal,
|
|
|
+ Children = { confirmButton, cancelButton },
|
|
|
+ }.SetGridRow(2),
|
|
|
},
|
|
|
},
|
|
|
},
|
|
|
};
|
|
|
|
|
|
- void UpdateMessage()
|
|
|
+ void UpdateMessage(double secondsLeft)
|
|
|
{
|
|
|
- messageTextBlock.Text = "当前客户端仍可连接到设备。是否确认保留这次网络配置?\n\n超时或取消时,Linux 端会自动回滚。";
|
|
|
- confirmButton.Content = $"保留({remaining}秒)";
|
|
|
+ remaining = Math.Max(0, (int)Math.Ceiling(secondsLeft));
|
|
|
+ remainingTextBlock.Inlines.Clear();
|
|
|
+ remainingTextBlock.Inlines.Add(new Run($"{remaining} 秒后")
|
|
|
+ {
|
|
|
+ Foreground = new SolidColorBrush(Color.FromRgb(24, 119, 242)),
|
|
|
+ });
|
|
|
+ remainingTextBlock.Inlines.Add(new Run("撤销修改")
|
|
|
+ {
|
|
|
+ Foreground = new SolidColorBrush(Color.FromRgb(31, 41, 55)),
|
|
|
+ });
|
|
|
+ progressBar.Value = Math.Max(0, secondsLeft);
|
|
|
}
|
|
|
|
|
|
- var timer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
|
|
+ var timer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) };
|
|
|
timer.Tick += (_, _) =>
|
|
|
{
|
|
|
- remaining--;
|
|
|
- if (remaining <= 0)
|
|
|
+ var secondsLeft = (deadline - DateTime.UtcNow).TotalSeconds;
|
|
|
+ if (secondsLeft <= 0)
|
|
|
{
|
|
|
timer.Stop();
|
|
|
dialog.DialogResult = false;
|
|
|
dialog.Close();
|
|
|
return;
|
|
|
}
|
|
|
- UpdateMessage();
|
|
|
+ UpdateMessage(secondsLeft);
|
|
|
};
|
|
|
confirmButton.Click += (_, _) =>
|
|
|
{
|
|
|
@@ -437,12 +473,83 @@ public partial class DeviceDetailsWindow : Window
|
|
|
};
|
|
|
dialog.Closed += (_, _) => timer.Stop();
|
|
|
|
|
|
- UpdateMessage();
|
|
|
+ UpdateMessage(timeoutSeconds);
|
|
|
timer.Start();
|
|
|
dialog.ShowDialog();
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+ private static Border CreateApplyConfirmNoticeCard()
|
|
|
+ {
|
|
|
+ return new Border
|
|
|
+ {
|
|
|
+ Height = 36,
|
|
|
+ Padding = new Thickness(14, 0, 14, 0),
|
|
|
+ Background = new SolidColorBrush(Color.FromRgb(255, 251, 235)),
|
|
|
+ BorderBrush = new SolidColorBrush(Color.FromRgb(253, 230, 138)),
|
|
|
+ BorderThickness = new Thickness(1),
|
|
|
+ CornerRadius = new CornerRadius(8),
|
|
|
+ Child = new StackPanel
|
|
|
+ {
|
|
|
+ VerticalAlignment = VerticalAlignment.Center,
|
|
|
+ Orientation = Orientation.Horizontal,
|
|
|
+ Children =
|
|
|
+ {
|
|
|
+ new TextBlock
|
|
|
+ {
|
|
|
+ Text = "⏳",
|
|
|
+ Margin = new Thickness(0, 0, 12, 0),
|
|
|
+ VerticalAlignment = VerticalAlignment.Center,
|
|
|
+ FontSize = 16,
|
|
|
+ Foreground = new SolidColorBrush(Color.FromRgb(245, 158, 11)),
|
|
|
+ },
|
|
|
+ new TextBlock
|
|
|
+ {
|
|
|
+ Text = "请在倒计时结束前点击“保留配置”。",
|
|
|
+ VerticalAlignment = VerticalAlignment.Center,
|
|
|
+ FontSize = 13,
|
|
|
+ Foreground = new SolidColorBrush(Color.FromRgb(31, 41, 55)),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }.SetGridRow(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Border CreateApplyConfirmCountdownCard(TextBlock remainingTextBlock, ProgressBar progressBar)
|
|
|
+ {
|
|
|
+ return new Border
|
|
|
+ {
|
|
|
+ Margin = new Thickness(0, 16, 0, 0),
|
|
|
+ Padding = new Thickness(18, 12, 18, 12),
|
|
|
+ BorderBrush = new SolidColorBrush(Color.FromRgb(229, 231, 235)),
|
|
|
+ BorderThickness = new Thickness(1),
|
|
|
+ CornerRadius = new CornerRadius(8),
|
|
|
+ Child = new StackPanel
|
|
|
+ {
|
|
|
+ Children =
|
|
|
+ {
|
|
|
+ new StackPanel
|
|
|
+ {
|
|
|
+ Orientation = Orientation.Horizontal,
|
|
|
+ Children =
|
|
|
+ {
|
|
|
+ new TextBlock
|
|
|
+ {
|
|
|
+ Text = "◷",
|
|
|
+ Margin = new Thickness(0, 0, 12, 0),
|
|
|
+ VerticalAlignment = VerticalAlignment.Center,
|
|
|
+ FontSize = 18,
|
|
|
+ Foreground = new SolidColorBrush(Color.FromRgb(24, 119, 242)),
|
|
|
+ },
|
|
|
+ remainingTextBlock,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ progressBar.SetMargin(new Thickness(0, 12, 0, 0)),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }.SetGridRow(1);
|
|
|
+ }
|
|
|
+
|
|
|
private async void RebootButton_OnClick(object sender, RoutedEventArgs e)
|
|
|
{
|
|
|
await ExecuteSystemActionAsync(
|
|
|
@@ -1017,7 +1124,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
_configValidated = false;
|
|
|
RefreshChangeState();
|
|
|
SetConfigStateMessage(
|
|
|
- _configDirty ? $"配置已修改:{FormatChangedFields()}。需重新校验后才能应用。" : "配置未修改。",
|
|
|
+ _configDirty ? "配置已修改,需重新校验后才能保存。" : "配置未修改。",
|
|
|
_configDirty);
|
|
|
UpdateButtonStates();
|
|
|
}
|
|
|
@@ -1134,16 +1241,16 @@ public partial class DeviceDetailsWindow : Window
|
|
|
{
|
|
|
return task.Status switch
|
|
|
{
|
|
|
- "success" => (string.IsNullOrWhiteSpace(task.Detail) ? "配置已成功应用。" : task.Detail, StatusMessageType.Success),
|
|
|
- "failed" => (string.IsNullOrWhiteSpace(task.Detail) ? "配置应用失败。" : task.Detail, StatusMessageType.Error),
|
|
|
- "rolled_back" => (string.IsNullOrWhiteSpace(task.Detail) ? "配置应用失败,已自动回滚。" : task.Detail, StatusMessageType.Error),
|
|
|
+ "success" => (string.IsNullOrWhiteSpace(task.Detail) ? "配置已成功保存。" : task.Detail, StatusMessageType.Success),
|
|
|
+ "failed" => (string.IsNullOrWhiteSpace(task.Detail) ? "配置保存失败。" : task.Detail, StatusMessageType.Error),
|
|
|
+ "rolled_back" => (string.IsNullOrWhiteSpace(task.Detail) ? "配置保存失败,已自动回滚。" : task.Detail, StatusMessageType.Error),
|
|
|
_ => task.Step switch
|
|
|
{
|
|
|
"validating" => ("正在校验配置...", StatusMessageType.Info),
|
|
|
"writing_netplan" => ("正在写入 Linux 网络配置...", StatusMessageType.Info),
|
|
|
- "applying" => ("正在应用 Linux 网络配置...", StatusMessageType.Info),
|
|
|
+ "applying" => ("正在保存 Linux 网络配置...", StatusMessageType.Info),
|
|
|
"confirming" => (string.IsNullOrWhiteSpace(task.Detail) ? "等待确认保留配置..." : task.Detail, StatusMessageType.Warning),
|
|
|
- "rolling_back" => ("配置应用失败,正在自动回滚...", StatusMessageType.Warning),
|
|
|
+ "rolling_back" => ("配置保存失败,正在自动回滚...", StatusMessageType.Warning),
|
|
|
_ => (string.IsNullOrWhiteSpace(task.Detail) ? "正在处理,请稍候..." : task.Detail, StatusMessageType.Info),
|
|
|
}
|
|
|
};
|
|
|
@@ -1152,7 +1259,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
private void ShowTaskCompletionDialog(RemoteTaskResult task)
|
|
|
{
|
|
|
var (message, _) = FormatTaskStatusMessage(task);
|
|
|
- var title = task.Status == "success" ? "应用配置成功" : "应用配置失败";
|
|
|
+ var title = task.Status == "success" ? "保存配置成功" : "保存配置失败";
|
|
|
var image = task.Status == "success" ? MessageBoxImage.Information : MessageBoxImage.Warning;
|
|
|
MessageBox.Show(this, message, title, MessageBoxButton.OK, image);
|
|
|
}
|
|
|
@@ -1447,3 +1554,18 @@ public partial class DeviceDetailsWindow : Window
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+internal static class ApplyConfirmationDialogElementExtensions
|
|
|
+{
|
|
|
+ public static T SetGridRow<T>(this T element, int row) where T : UIElement
|
|
|
+ {
|
|
|
+ Grid.SetRow(element, row);
|
|
|
+ return element;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static T SetMargin<T>(this T element, Thickness margin) where T : FrameworkElement
|
|
|
+ {
|
|
|
+ element.Margin = margin;
|
|
|
+ return element;
|
|
|
+ }
|
|
|
+}
|