Build / build (push) Canceled after 0s
MndpDiscovery (UDP 5678, Broadcast-Anfrage + Antworten 4 s sammeln, TLV-Parser wie Mac v1.6.0) findet Router ueber die MAC-Adresse, auch ohne passende IP. Treffer zeigen Name/Modell/IP/MAC; 'IP uebernehmen' fuellt die Adresse, 'MAC kopieren' mit Hinweis auf WinBox bei Routern ohne IP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
225 lines
7.5 KiB
C#
225 lines
7.5 KiB
C#
using System.Collections.ObjectModel;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using MikrotikInstaller.App.Credentials;
|
|
using MikrotikInstaller.Core.Connectivity;
|
|
using MikrotikInstaller.Core.Connectivity.Demo;
|
|
using MikrotikInstaller.Core.Devices;
|
|
|
|
namespace MikrotikInstaller.App.ViewModels.Steps;
|
|
|
|
/// <summary>
|
|
/// Erster Schritt: Verbindungsdaten zum MikroTik-Gerät erfassen und testen — nur noch für neue,
|
|
/// unbekannte Geräte gedacht. "Bekannte Router" leben seit dem Sidebar-Umbau (MainWindow.xaml)
|
|
/// dauerhaft dort als Kurzauswahl statt hier im Hauptfenster verdoppelt zu werden; dieses ViewModel
|
|
/// hält <see cref="SavedRouters"/> aber weiterhin (die Sidebar bindet direkt über
|
|
/// <c>MainViewModel.Connection</c> darauf) und stellt <see cref="ConnectToSavedRouterCommand"/>/
|
|
/// <see cref="RemoveSavedRouterCommand"/> dafür bereit. Ein Eintrag wird erst im nächsten Schritt
|
|
/// (Geräte-Erkennung) tatsächlich gespeichert/aktualisiert — dort sind Modell und Seriennummer für
|
|
/// den Namensvorschlag bzw. die Unterscheidung zweier Geräte mit gleichem Host+Benutzername bekannt.
|
|
/// </summary>
|
|
public partial class ConnectionStepViewModel : WizardStepViewModelBase
|
|
{
|
|
private readonly WizardSession _session;
|
|
private readonly SavedRoutersStore _savedRoutersStore = new();
|
|
|
|
public ConnectionStepViewModel(WizardSession session)
|
|
: base(
|
|
title: "Verbindung",
|
|
description: "Gib die Adresse deines MikroTik-Geräts sowie Benutzername und Passwort ein.")
|
|
{
|
|
_session = session;
|
|
RefreshSavedRouters();
|
|
}
|
|
|
|
public ObservableCollection<SavedRouterEntry> SavedRouters { get; } = [];
|
|
|
|
public bool HasSavedRouters => SavedRouters.Count > 0;
|
|
|
|
/// <summary>Simulierte Testgeräte (Mac v1.8.0 „Testumgebung"), abschaltbar in den Einstellungen.</summary>
|
|
public IReadOnlyList<DemoDeviceProfile> TestDevices => DemoRouterOsClient.Profiles;
|
|
|
|
public bool ShowTestEnvironment => UserSettings.Current.TestEnvironment;
|
|
|
|
/// <summary>Per MNDP gefundene Router (wie „Neighbors" in WinBox), Mac v1.6.0.</summary>
|
|
public ObservableCollection<MndpNeighbor> Neighbors { get; } = [];
|
|
|
|
[ObservableProperty]
|
|
private bool _isSearchingNeighbors;
|
|
|
|
[ObservableProperty]
|
|
private string? _neighborSearchMessage;
|
|
|
|
[RelayCommand]
|
|
private async Task SearchNeighborsAsync()
|
|
{
|
|
IsSearchingNeighbors = true;
|
|
NeighborSearchMessage = null;
|
|
Neighbors.Clear();
|
|
try
|
|
{
|
|
foreach (var neighbor in await MndpDiscovery.DiscoverAsync(TimeSpan.FromSeconds(4)))
|
|
{
|
|
Neighbors.Add(neighbor);
|
|
}
|
|
|
|
NeighborSearchMessage = Neighbors.Count == 0
|
|
? "Kein MikroTik-Router gefunden. Ist der PC per Kabel oder WLAN mit dem Router verbunden?"
|
|
: null;
|
|
}
|
|
catch (System.Net.Sockets.SocketException)
|
|
{
|
|
NeighborSearchMessage = "Die Router-Suche ist gerade nicht möglich. Läuft auf diesem PC ein anderes Programm, das Port 5678 belegt?";
|
|
}
|
|
finally
|
|
{
|
|
IsSearchingNeighbors = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void UseNeighbor(MndpNeighbor neighbor)
|
|
{
|
|
if (neighbor.IPv4Address is { } address)
|
|
{
|
|
Host = address;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void CopyMac(MndpNeighbor neighbor) => System.Windows.Clipboard.SetText(neighbor.MacAddress);
|
|
|
|
[RelayCommand]
|
|
private async Task ConnectToTestDeviceAsync(DemoDeviceProfile profile)
|
|
{
|
|
Host = profile.Host;
|
|
Username = DemoRouterOsClient.DemoUsername;
|
|
Password = DemoRouterOsClient.DemoPassword;
|
|
UseEncryption = false;
|
|
await TestConnectionAsync();
|
|
}
|
|
|
|
[ObservableProperty]
|
|
private string _host = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string _username = "admin";
|
|
|
|
[ObservableProperty]
|
|
private string _password = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private bool _useEncryption;
|
|
|
|
[ObservableProperty]
|
|
private bool _rememberPassword = true;
|
|
|
|
[ObservableProperty]
|
|
private bool _isTesting;
|
|
|
|
[ObservableProperty]
|
|
private string? _statusMessage;
|
|
|
|
[ObservableProperty]
|
|
private bool _statusIsError;
|
|
|
|
[ObservableProperty]
|
|
[NotifyPropertyChangedFor(nameof(CanGoNext))]
|
|
private bool _isConnected;
|
|
|
|
public override bool CanGoNext => IsConnected;
|
|
|
|
private bool CanTestConnection => !IsTesting && !string.IsNullOrWhiteSpace(Host) && !string.IsNullOrWhiteSpace(Username);
|
|
|
|
public override Task OnActivatedAsync()
|
|
{
|
|
RefreshSavedRouters();
|
|
OnPropertyChanged(nameof(ShowTestEnvironment));
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private void RefreshSavedRouters()
|
|
{
|
|
SavedRouters.Clear();
|
|
var routers = _savedRoutersStore.Load().OrderByDescending(r => r.LastConnectedAt);
|
|
|
|
foreach (var router in routers)
|
|
{
|
|
SavedRouters.Add(new SavedRouterEntry(router));
|
|
}
|
|
|
|
OnPropertyChanged(nameof(HasSavedRouters));
|
|
}
|
|
|
|
/// <summary>Nur noch intern von <see cref="ConnectToSavedRouterAsync"/> genutzt — Name/Standort werden nirgends mehr manuell bearbeitet, daher kein eigenes Kommando mehr dafür.</summary>
|
|
private void UseSavedRouter(SavedRouterEntry entry)
|
|
{
|
|
Host = entry.Host;
|
|
Username = entry.Username;
|
|
var storedPassword = WindowsCredentialStore.TryRead(entry.Id);
|
|
Password = storedPassword ?? string.Empty;
|
|
RememberPassword = storedPassword is not null;
|
|
}
|
|
|
|
/// <summary>Ein-Klick-Verbinden für die Sidebar-Kurzauswahl — übernimmt die Zugangsdaten und verbindet direkt, ohne den Umweg über die Formularfelder.</summary>
|
|
[RelayCommand]
|
|
private async Task ConnectToSavedRouterAsync(SavedRouterEntry entry)
|
|
{
|
|
UseSavedRouter(entry);
|
|
await TestConnectionAsync();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void RemoveSavedRouter(SavedRouterEntry entry)
|
|
{
|
|
_savedRoutersStore.Remove(entry.Id);
|
|
WindowsCredentialStore.Delete(entry.Id);
|
|
SavedRouters.Remove(entry);
|
|
OnPropertyChanged(nameof(HasSavedRouters));
|
|
}
|
|
|
|
[RelayCommand(CanExecute = nameof(CanTestConnection))]
|
|
private async Task TestConnectionAsync()
|
|
{
|
|
IsTesting = true;
|
|
StatusMessage = "Verbindung wird getestet …";
|
|
StatusIsError = false;
|
|
IsConnected = false;
|
|
|
|
await _session.DisposeClientAsync();
|
|
_session.Device = null;
|
|
|
|
try
|
|
{
|
|
var credentials = new RouterOsCredentials(Host.Trim(), Username.Trim(), Password)
|
|
{
|
|
UseEncryption = UseEncryption,
|
|
};
|
|
|
|
_session.Client = await RouterOsClientFactory.ConnectAsync(credentials);
|
|
_session.Credentials = credentials;
|
|
_session.RememberPassword = RememberPassword;
|
|
|
|
IsConnected = true;
|
|
StatusIsError = false;
|
|
StatusMessage = "Verbindung erfolgreich hergestellt.";
|
|
}
|
|
catch (RouterOsException ex)
|
|
{
|
|
IsConnected = false;
|
|
StatusIsError = true;
|
|
StatusMessage = ex.Message;
|
|
}
|
|
finally
|
|
{
|
|
IsTesting = false;
|
|
}
|
|
}
|
|
|
|
partial void OnHostChanged(string value) => TestConnectionCommand.NotifyCanExecuteChanged();
|
|
|
|
partial void OnUsernameChanged(string value) => TestConnectionCommand.NotifyCanExecuteChanged();
|
|
|
|
partial void OnIsTestingChanged(bool value) => TestConnectionCommand.NotifyCanExecuteChanged();
|
|
}
|