using System.Collections.Generic; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; using NetworkTool.Client.Models; using NetworkTool.Client.Services; namespace NetworkTool.Client; public partial class MainWindow : Window { private readonly NetworkAdapterService _networkAdapterService = new(); private readonly PasswordStoreService _passwordStoreService = new(); private readonly NetworkConfigurationService _networkConfigurationService = new(); private readonly DiscoveryService _discoveryService = new(); private readonly ServerApiService _serverApiService = new(); private readonly AdminPrivilegeService _adminPrivilegeService = new(); private IReadOnlyList _adapters = []; private bool _isShowingPassword; private bool _isBusy; private bool _suppressPasswordSync; private CancellationTokenSource? _statusMessageCts; public MainWindow() { InitializeComponent(); Loaded += MainWindow_OnLoaded; } private async void MainWindow_OnLoaded(object sender, RoutedEventArgs e) { await LoadInitialStateAsync(); } private async Task LoadInitialStateAsync() { AdminStateTextBlock.Text = _adminPrivilegeService.IsAdministrator() ? "管理员状态:当前已以管理员身份运行,可执行本机网卡切换。" : "管理员状态:当前不是管理员运行,切换到维护网络会失败。"; _adapters = _networkAdapterService.GetEthernetAdapters(); await _networkAdapterService.ProbeMaintenanceReachabilityAsync(_adapters); _adapters = _adapters .OrderByDescending(adapter => adapter.RecommendationScore) .ThenBy(adapter => adapter.Name) .ToList(); AdapterComboBox.ItemsSource = _adapters; var recommendedAdapter = _networkAdapterService.GetRecommendedAdapter(_adapters); var savedPassword = _passwordStoreService.LoadPassword(); if (!string.IsNullOrWhiteSpace(savedPassword)) { PasswordBox.Password = savedPassword; PasswordTextBox.Text = savedPassword; } if (recommendedAdapter is not null) { AdapterComboBox.SelectedItem = recommendedAdapter; UpdateAdapterDetails(recommendedAdapter); } else if (_adapters.Count > 0) { AdapterComboBox.SelectedIndex = 0; UpdateAdapterDetails(_adapters[0]); } AppendLog("客户端已加载连接页。", true); UpdateButtonStates(); } private void AdapterComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { if (AdapterComboBox.SelectedItem is not AdapterInfo adapter) { UpdateAdapterDetails(null); SetStatus("请选择一块有线网卡。", false); UpdateButtonStates(); return; } UpdateAdapterDetails(adapter); SetStatus(adapter.HasLink ? $"已选择 {adapter.RecommendationLabel} 网卡,可切换到维护网络。{adapter.RecommendationReason}" : "当前网卡未检测到链路,请检查网线连接。", true); UpdateButtonStates(); } private async void SwitchMaintenanceButton_OnClick(object sender, RoutedEventArgs e) { if (!_adminPrivilegeService.IsAdministrator()) { SetStatus("当前程序未以管理员身份运行,无法修改本机网卡。", true); MessageBox.Show(this, "请以管理员身份运行客户端后再切换到维护网络。", "需要管理员权限", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (AdapterComboBox.SelectedItem is not AdapterInfo adapter) { SetStatus("请先选择一块网卡。", true); return; } PersistPasswordIfNeeded(); if (adapter.IsReachableToMaintenance) { SetStatus("当前网卡已经可以直接访问 169.254.100.2,无需切换到维护网络。", true); return; } SetBusyState(true); SetStatus("正在切换到维护网络,请稍候。", true); await Dispatcher.InvokeAsync(() => { }, System.Windows.Threading.DispatcherPriority.Render); try { await _networkConfigurationService.ConfigureMaintenanceNetworkAsync(adapter); SetStatus("已切换到维护网络。", true); await RefreshAdaptersAsync(adapter.Id); } catch (Exception ex) { SetStatus($"切换维护网络失败:{ex.Message}", true); MessageBox.Show(this, ex.Message, "切换维护网络失败", MessageBoxButton.OK, MessageBoxImage.Error); } finally { SetBusyState(false); } } private async void RefreshAdaptersButton_OnClick(object sender, RoutedEventArgs e) { SetBusyState(true); try { await RefreshAdaptersAsync(); SetStatus("已刷新本机网卡和管理口探测结果。", true); } catch (Exception ex) { SetStatus($"刷新本机网卡失败:{ex.Message}", true); MessageBox.Show(this, ex.Message, "刷新失败", MessageBoxButton.OK, MessageBoxImage.Error); } finally { SetBusyState(false); } } private async void DiscoverConnectButton_OnClick(object sender, RoutedEventArgs e) { if (string.IsNullOrWhiteSpace(GetCurrentPassword())) { SetStatus("请输入管理密码。", true); MessageBox.Show(this, "请先在右侧“管理密码(必填)”区域输入密码。", "缺少管理密码", MessageBoxButton.OK, MessageBoxImage.Information); return; } PersistPasswordIfNeeded(); SetBusyState(true); SetStatus("正在检查当前网卡是否可直接访问管理口。", true); await Dispatcher.InvokeAsync(() => { }, System.Windows.Threading.DispatcherPriority.Render); try { if (AdapterComboBox.SelectedItem is AdapterInfo adapter && adapter.IsReachableToMaintenance) { var directResult = await _serverApiService.CheckHealthAsync("http://169.254.100.2:48888", GetCurrentPassword(), adapter.IPv4Address); if (directResult.Success) { SetStatus("连接成功,无需切换本机网卡。", true); OpenDeviceDetailsWindow("http://169.254.100.2:48888", adapter.IPv4Address); return; } if (directResult.StatusCode == 401) { SetStatus("该网卡可以直连管理口,但管理密码错误。", true); MessageBox.Show(this, "当前网卡已经可以直连 Server,但密码校验失败,请确认密码是否正确。", "密码错误", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (directResult.StatusCode == 403) { SetStatus("该网卡可以直连管理口,但 Server 拒绝访问,请确认远端是否还是旧版本。", true); MessageBox.Show(this, "当前网卡已经可以直连 Server,但请求被拒绝。请确认 Linux 端是否运行的是最新版本 Server。", "访问被拒绝", MessageBoxButton.OK, MessageBoxImage.Warning); return; } SetStatus($"该网卡虽可建立连接,但直连 HTTP 校验失败:{directResult.Message}。正在尝试设备发现。", true); } SetStatus("当前网卡无法完成直连校验,正在发现设备,请稍候。", true); var selectedAdapter = AdapterComboBox.SelectedItem as AdapterInfo; var device = await _discoveryService.DiscoverAsync(selectedAdapter?.IPv4Address ?? string.Empty); if (device is null) { SetStatus("未发现设备。如果当前网卡不可达,请先切换到维护网络。", true); return; } SetStatus("已发现设备,正在验证连接。", true); var discoveredResult = await _serverApiService.CheckHealthAsync($"http://{device.Lan2Ip}:48888", GetCurrentPassword(), selectedAdapter?.IPv4Address ?? string.Empty); if (discoveredResult.Success) { SetStatus("连接成功。", true); OpenDeviceDetailsWindow($"http://{device.Lan2Ip}:48888", selectedAdapter?.IPv4Address ?? string.Empty); } else { SetStatus($"设备已发现,但 HTTP 验证失败:{discoveredResult.Message}", true); } } catch (Exception ex) { SetStatus($"连接失败:{ex.Message}", true); MessageBox.Show(this, ex.Message, "连接失败", MessageBoxButton.OK, MessageBoxImage.Error); } finally { SetBusyState(false); } } private void PersistPasswordIfNeeded() { var password = GetCurrentPassword(); if (!string.IsNullOrWhiteSpace(password)) { _passwordStoreService.SavePassword(password); return; } } private string GetCurrentPassword() { return _isShowingPassword ? PasswordTextBox.Text : PasswordBox.Password; } private void UpdateButtonStates() { var adapter = AdapterComboBox.SelectedItem as AdapterInfo; var hasAdapter = adapter is not null; RefreshAdaptersButton.IsEnabled = !_isBusy; SwitchMaintenanceButton.IsEnabled = !_isBusy && hasAdapter; DiscoverConnectButton.IsEnabled = !_isBusy && hasAdapter && adapter!.HasLink; } private async Task RefreshAdaptersAsync(string? selectedAdapterId = null) { _adapters = _networkAdapterService.GetEthernetAdapters(); await _networkAdapterService.ProbeMaintenanceReachabilityAsync(_adapters); _adapters = _adapters .OrderByDescending(adapter => adapter.RecommendationScore) .ThenBy(adapter => adapter.Name) .ToList(); AdapterComboBox.ItemsSource = _adapters; var selected = selectedAdapterId is null ? _networkAdapterService.GetRecommendedAdapter(_adapters) : _adapters.FirstOrDefault(adapter => adapter.Id == selectedAdapterId) ?? _networkAdapterService.GetRecommendedAdapter(_adapters); if (selected is not null) { AdapterComboBox.SelectedItem = selected; UpdateAdapterDetails(selected); } } private void UpdateAdapterDetails(AdapterInfo? adapter) { if (adapter is null) { AdapterProbeTextBlock.Text = "-"; return; } AdapterProbeTextBlock.Text = $"{adapter.ProbeStatus} / {adapter.ProbeReason}"; } private void OpenDeviceDetailsWindow(string baseAddress, string localIPv4) { var window = new DeviceDetailsWindow(baseAddress, localIPv4, GetCurrentPassword()) { Owner = this, }; window.ShowDialog(); } private void SetStatus(string message, bool addLog) { ApplyStatusMessageStyle(message); StatusTextBlock.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); if (addLog) { AppendLog(message, false); } } 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; StatusTextBlock.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 AppendLog(string message, bool isInitial) { var prefix = DateTime.Now.ToString("HH:mm:ss", CultureInfo.InvariantCulture); EventLogListBox.Items.Add($"[{prefix}] {message}"); if (!isInitial) { EventLogListBox.ScrollIntoView(EventLogListBox.Items[^1]); } } private void SetBusyState(bool isBusy) { _isBusy = isBusy; AdapterComboBox.IsEnabled = !isBusy; RefreshAdaptersButton.IsEnabled = !isBusy; PasswordBox.IsEnabled = !isBusy; PasswordTextBox.IsEnabled = !isBusy; TogglePasswordVisibilityButton.IsEnabled = !isBusy; SwitchMaintenanceButton.IsEnabled = !isBusy && AdapterComboBox.SelectedItem is AdapterInfo; DiscoverConnectButton.IsEnabled = !isBusy && AdapterComboBox.SelectedItem is AdapterInfo adapter && adapter.HasLink; } private void TogglePasswordVisibilityButton_OnClick(object sender, RoutedEventArgs e) { _isShowingPassword = !_isShowingPassword; if (_isShowingPassword) { PasswordTextBox.Text = PasswordBox.Password; PasswordBox.Visibility = Visibility.Collapsed; PasswordTextBox.Visibility = Visibility.Visible; TogglePasswordVisibilityButton.Content = "🙈"; PasswordTextBox.Focus(); PasswordTextBox.CaretIndex = PasswordTextBox.Text.Length; return; } PasswordBox.Password = PasswordTextBox.Text; PasswordTextBox.Visibility = Visibility.Collapsed; PasswordBox.Visibility = Visibility.Visible; TogglePasswordVisibilityButton.Content = "👁"; PasswordBox.Focus(); } private void PasswordBox_OnPasswordChanged(object sender, RoutedEventArgs e) { if (_suppressPasswordSync) { return; } _suppressPasswordSync = true; PasswordTextBox.Text = PasswordBox.Password; _suppressPasswordSync = false; } private void PasswordTextBox_OnTextChanged(object sender, TextChangedEventArgs e) { if (_suppressPasswordSync) { return; } _suppressPasswordSync = true; PasswordBox.Password = PasswordTextBox.Text; _suppressPasswordSync = false; } }