|
|
@@ -152,6 +152,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
editor.Dns.Add(new EditableDns(editor) { Address = dns });
|
|
|
}
|
|
|
}
|
|
|
+ editor.CaptureOriginalConfiguration();
|
|
|
_suppressConfigChangeHandling = false;
|
|
|
UpdateButtonStates();
|
|
|
}
|
|
|
@@ -253,7 +254,10 @@ public partial class DeviceDetailsWindow : Window
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- var confirmMessage = "将要一次性应用以下接口配置:\n\n" + FormatConfigSummary(request) + "\n\n请确认是否继续。";
|
|
|
+ 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)
|
|
|
{
|
|
|
return;
|
|
|
@@ -582,6 +586,12 @@ public partial class DeviceDetailsWindow : Window
|
|
|
$"DNS:{(item.Dns.Count == 0 ? "无" : string.Join(", ", item.Dns))}"));
|
|
|
}
|
|
|
|
|
|
+ private string FormatChangeSummary()
|
|
|
+ {
|
|
|
+ var changed = _interfaces.Where(item => item.HasChanges).Select(item => item.FormatChangeSummary()).Where(item => item != string.Empty);
|
|
|
+ return string.Join(Environment.NewLine + Environment.NewLine, changed);
|
|
|
+ }
|
|
|
+
|
|
|
private static EditableRoute CreateEditableRoute(InterfaceEditor owner, RemoteInterfaceRouteConfig route)
|
|
|
{
|
|
|
var to = route.To.Trim();
|
|
|
@@ -846,15 +856,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
|
|
|
private void ConfigInputChanged_OnChanged(object sender, TextChangedEventArgs e)
|
|
|
{
|
|
|
- if (_suppressConfigChangeHandling)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- _configValidated = false;
|
|
|
- _configDirty = true;
|
|
|
- SetConfigStateMessage("配置已修改,需重新校验后才能应用。", true);
|
|
|
- UpdateButtonStates();
|
|
|
+ MarkConfigChanged();
|
|
|
}
|
|
|
|
|
|
private void ConfigModeChanged_OnChanged(object sender, RoutedEventArgs e)
|
|
|
@@ -865,10 +867,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- _configValidated = false;
|
|
|
- _configDirty = true;
|
|
|
- SetConfigStateMessage("配置已修改,需重新校验后才能应用。", true);
|
|
|
- UpdateButtonStates();
|
|
|
+ MarkConfigChanged();
|
|
|
}
|
|
|
|
|
|
private void GatewayOrRouteModeChanged_OnChanged(object sender, RoutedEventArgs e)
|
|
|
@@ -879,10 +878,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- _configValidated = false;
|
|
|
- _configDirty = true;
|
|
|
- SetConfigStateMessage("配置已修改,需重新校验后才能应用。", true);
|
|
|
- UpdateButtonStates();
|
|
|
+ MarkConfigChanged();
|
|
|
}
|
|
|
|
|
|
private void ConfigGrid_OnCellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
|
|
|
@@ -980,11 +976,29 @@ public partial class DeviceDetailsWindow : Window
|
|
|
}
|
|
|
|
|
|
_configValidated = false;
|
|
|
- _configDirty = true;
|
|
|
- SetConfigStateMessage("配置已修改,需重新校验后才能应用。", true);
|
|
|
+ RefreshChangeState();
|
|
|
+ SetConfigStateMessage(
|
|
|
+ _configDirty ? $"配置已修改:{FormatChangedFields()}。需重新校验后才能应用。" : "配置未修改。",
|
|
|
+ _configDirty);
|
|
|
UpdateButtonStates();
|
|
|
}
|
|
|
|
|
|
+ private void RefreshChangeState()
|
|
|
+ {
|
|
|
+ foreach (var editor in _interfaces)
|
|
|
+ {
|
|
|
+ editor.NotifyChangeState();
|
|
|
+ }
|
|
|
+
|
|
|
+ _configDirty = _interfaces.Any(item => item.HasChanges);
|
|
|
+ }
|
|
|
+
|
|
|
+ private string FormatChangedFields()
|
|
|
+ {
|
|
|
+ var fields = _interfaces.Where(item => item.HasChanges).Select(item => $"{item.SystemName} 的 {item.ChangedFieldsText}");
|
|
|
+ return string.Join(";", fields);
|
|
|
+ }
|
|
|
+
|
|
|
private void SetConfigStateMessage(string message, bool requiresAttention)
|
|
|
{
|
|
|
ConfigStateTextBlock.Text = message;
|
|
|
@@ -1129,6 +1143,10 @@ public partial class DeviceDetailsWindow : Window
|
|
|
private bool _defaultGatewayEnabled;
|
|
|
private bool _customRoutesEnabled;
|
|
|
private string _defaultGateway = string.Empty;
|
|
|
+ private bool _originalDhcp4;
|
|
|
+ private string[] _originalAddressKeys = [];
|
|
|
+ private string[] _originalGatewayKeys = [];
|
|
|
+ private string[] _originalDnsKeys = [];
|
|
|
|
|
|
public InterfaceEditor(RemoteInterfaceInfo info)
|
|
|
{
|
|
|
@@ -1143,6 +1161,16 @@ public partial class DeviceDetailsWindow : Window
|
|
|
public ObservableCollection<EditableAddress> Addresses { get; } = [];
|
|
|
public ObservableCollection<EditableRoute> Routes { get; } = [];
|
|
|
public ObservableCollection<EditableDns> Dns { get; } = [];
|
|
|
+ public bool IsAddressModified => _originalDhcp4 != Dhcp4 || !GetAddressKeys().SequenceEqual(_originalAddressKeys);
|
|
|
+ public bool IsGatewayModified => _originalDhcp4 != Dhcp4 || !GetGatewayKeys().SequenceEqual(_originalGatewayKeys);
|
|
|
+ public bool IsDnsModified => !GetDnsKeys().SequenceEqual(_originalDnsKeys);
|
|
|
+ public bool HasChanges => IsAddressModified || IsGatewayModified || IsDnsModified;
|
|
|
+ public string ChangedFieldsText => string.Join("、", new[]
|
|
|
+ {
|
|
|
+ IsAddressModified ? "IP 地址" : string.Empty,
|
|
|
+ IsGatewayModified ? "网关" : string.Empty,
|
|
|
+ IsDnsModified ? "DNS" : string.Empty,
|
|
|
+ }.Where(item => item != string.Empty));
|
|
|
|
|
|
public bool Dhcp4
|
|
|
{
|
|
|
@@ -1170,6 +1198,78 @@ public partial class DeviceDetailsWindow : Window
|
|
|
|
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
|
|
+ public void CaptureOriginalConfiguration()
|
|
|
+ {
|
|
|
+ _originalDhcp4 = Dhcp4;
|
|
|
+ _originalAddressKeys = GetAddressKeys();
|
|
|
+ _originalGatewayKeys = GetGatewayKeys();
|
|
|
+ _originalDnsKeys = GetDnsKeys();
|
|
|
+ NotifyChangeState();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void NotifyChangeState()
|
|
|
+ {
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsAddressModified)));
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsGatewayModified)));
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsDnsModified)));
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasChanges)));
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ChangedFieldsText)));
|
|
|
+ }
|
|
|
+
|
|
|
+ public string FormatChangeSummary()
|
|
|
+ {
|
|
|
+ var lines = new List<string> { $"接口:{SystemName}" };
|
|
|
+ if (IsAddressModified)
|
|
|
+ {
|
|
|
+ lines.Add($"IP:{FormatKeys(_originalAddressKeys)} -> {FormatKeys(GetAddressKeys())}");
|
|
|
+ }
|
|
|
+ if (IsGatewayModified)
|
|
|
+ {
|
|
|
+ lines.Add($"网关:{FormatKeys(_originalGatewayKeys)} -> {FormatKeys(GetGatewayKeys())}");
|
|
|
+ }
|
|
|
+ if (IsDnsModified)
|
|
|
+ {
|
|
|
+ lines.Add($"DNS:{FormatKeys(_originalDnsKeys)} -> {FormatKeys(GetDnsKeys())}");
|
|
|
+ }
|
|
|
+
|
|
|
+ return lines.Count == 1 ? string.Empty : string.Join(Environment.NewLine, lines);
|
|
|
+ }
|
|
|
+
|
|
|
+ private string[] GetAddressKeys()
|
|
|
+ {
|
|
|
+ return Addresses
|
|
|
+ .Select(item => $"{item.IP.Trim()}/{item.Mask.Trim()}")
|
|
|
+ .Where(item => item != "/")
|
|
|
+ .ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ private string[] GetGatewayKeys()
|
|
|
+ {
|
|
|
+ var keys = new List<string>();
|
|
|
+ if (DefaultGatewayEnabled || !string.IsNullOrWhiteSpace(DefaultGateway))
|
|
|
+ {
|
|
|
+ keys.Add($"default via {DefaultGateway.Trim()}");
|
|
|
+ }
|
|
|
+ if (CustomRoutesEnabled)
|
|
|
+ {
|
|
|
+ keys.AddRange(Routes
|
|
|
+ .Select(item => $"{item.To.Trim()}/{item.Mask.Trim()} via {item.Via.Trim()}")
|
|
|
+ .Where(item => item != "/ via"));
|
|
|
+ }
|
|
|
+
|
|
|
+ return keys.ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ private string[] GetDnsKeys()
|
|
|
+ {
|
|
|
+ return Dns.Select(item => item.Address.Trim()).Where(item => item != string.Empty).ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string FormatKeys(IReadOnlyList<string> keys)
|
|
|
+ {
|
|
|
+ return keys.Count == 0 ? "无" : string.Join(", ", keys);
|
|
|
+ }
|
|
|
+
|
|
|
private void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
|
|
|
{
|
|
|
if (EqualityComparer<T>.Default.Equals(field, value))
|
|
|
@@ -1178,6 +1278,7 @@ public partial class DeviceDetailsWindow : Window
|
|
|
}
|
|
|
field = value;
|
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
+ NotifyChangeState();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1215,30 +1316,83 @@ public partial class DeviceDetailsWindow : Window
|
|
|
}
|
|
|
field = value;
|
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
+ Owner.NotifyChangeState();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private sealed class EditableRoute
|
|
|
+ private sealed class EditableRoute : INotifyPropertyChanged
|
|
|
{
|
|
|
+ private string _to = string.Empty;
|
|
|
+ private string _mask = string.Empty;
|
|
|
+ private string _via = string.Empty;
|
|
|
+
|
|
|
public EditableRoute(InterfaceEditor owner)
|
|
|
{
|
|
|
Owner = owner;
|
|
|
}
|
|
|
|
|
|
public InterfaceEditor Owner { get; }
|
|
|
- public string To { get; set; } = string.Empty;
|
|
|
- public string Mask { get; set; } = string.Empty;
|
|
|
- public string Via { get; set; } = string.Empty;
|
|
|
+
|
|
|
+ public string To
|
|
|
+ {
|
|
|
+ get => _to;
|
|
|
+ set => SetField(ref _to, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public string Mask
|
|
|
+ {
|
|
|
+ get => _mask;
|
|
|
+ set => SetField(ref _mask, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public string Via
|
|
|
+ {
|
|
|
+ get => _via;
|
|
|
+ set => SetField(ref _via, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
+
|
|
|
+ private void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
|
|
|
+ {
|
|
|
+ if (EqualityComparer<T>.Default.Equals(field, value))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ field = value;
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
+ Owner.NotifyChangeState();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- private sealed class EditableDns
|
|
|
+ private sealed class EditableDns : INotifyPropertyChanged
|
|
|
{
|
|
|
+ private string _address = string.Empty;
|
|
|
+
|
|
|
public EditableDns(InterfaceEditor owner)
|
|
|
{
|
|
|
Owner = owner;
|
|
|
}
|
|
|
|
|
|
public InterfaceEditor Owner { get; }
|
|
|
- public string Address { get; set; } = string.Empty;
|
|
|
+
|
|
|
+ public string Address
|
|
|
+ {
|
|
|
+ get => _address;
|
|
|
+ set => SetField(ref _address, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
+
|
|
|
+ private void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
|
|
|
+ {
|
|
|
+ if (EqualityComparer<T>.Default.Equals(field, value))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ field = value;
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
+ Owner.NotifyChangeState();
|
|
|
+ }
|
|
|
}
|
|
|
}
|