Files
Mikrotik-Installer/src/MikrotikInstaller.App/ViewModels/Steps/ConnectionStepViewModel.cs
T
adminandClaude Opus 5.5 c40f2bdaf9
Build / build (push) Canceled after 0s
Verbinden: Router im Netzwerk finden per MNDP (#53)
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>
2026-10-16 06:53:33 +02:00

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();
}