Einstellungen-Fenster: Erscheinungsbild, Update-Pruefung, Professioneller Netzwerker, Testumgebung, Backup-Ordner/-Passwort (#48)
Build / build (push) Canceled after 0s
Build / build (push) Canceled after 0s
- AppSettings/AppSettingsStore (JSON unter %AppData%), Backup-Passwort im Anmeldeinformationsspeicher; Speicherfehler wird angezeigt statt still ignoriert - Hell/Dunkel/Wie System, System-Watcher nur bei 'Wie System' - Experten-Modus im Assistenten nur mit 'Professioneller Netzwerker' (Mac v1.3.0) - Update-Pruefung beim Verbinden abschaltbar Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3346129652
commit
2a32034b63
12 files changed
+412
-10
No files matched your search
@@ -14,11 +14,8 @@ public partial class App : Application
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
// Übernimmt Hell/Dunkel vom Windows-Systemthema; die Akzentfarbe setzen wir
|
||||
// anschließend selbst auf MikroTik-Grün statt auf die Windows-Akzentfarbe.
|
||||
ApplicationThemeManager.ApplySystemTheme(updateAccent: false);
|
||||
var appliedTheme = ApplicationThemeManager.GetAppTheme();
|
||||
ApplicationAccentColorManager.Apply(BrandAccent, appliedTheme, systemGlassColor: false, systemAccentColor: false);
|
||||
// Hell/Dunkel laut Einstellungen (Standard: wie Windows); die Akzentfarbe ist immer MikroTik-Grün.
|
||||
UserSettings.ApplyTheme();
|
||||
|
||||
DispatcherUnhandledException += OnDispatcherUnhandledException;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="4,0,0,20">
|
||||
@@ -189,6 +190,13 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<ui:Button Grid.Row="3" Content="Einstellungen" Appearance="Transparent" HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left" Margin="0,12,0,0" Click="OnOpenSettings">
|
||||
<ui:Button.Icon>
|
||||
<ui:SymbolIcon Symbol="Settings24" />
|
||||
</ui:Button.Icon>
|
||||
</ui:Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
|
||||
@@ -16,11 +16,24 @@ public partial class MainWindow : FluentWindow
|
||||
viewModel.CloseRequested += (_, _) => Close();
|
||||
}
|
||||
|
||||
// Folgt Hell/Dunkel-Wechseln des Windows-Systemthemes zur Laufzeit;
|
||||
// Folgt Hell/Dunkel-Wechseln des Windows-Systemthemes zur Laufzeit — nur bei "Wie System";
|
||||
// updateAccents:false, damit unser MikroTik-Grün-Akzent dabei erhalten bleibt.
|
||||
SystemThemeWatcher.Watch(this, WindowBackdropType.Mica, updateAccents: false);
|
||||
WatchSystemThemeIfWanted();
|
||||
UserSettings.Changed += (_, _) => WatchSystemThemeIfWanted();
|
||||
}
|
||||
|
||||
private void WatchSystemThemeIfWanted()
|
||||
{
|
||||
SystemThemeWatcher.UnWatch(this);
|
||||
if (UserSettings.Current.Theme == Core.Settings.AppTheme.System)
|
||||
{
|
||||
SystemThemeWatcher.Watch(this, WindowBackdropType.Mica, updateAccents: false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenSettings(object sender, System.Windows.RoutedEventArgs e) =>
|
||||
new Views.SettingsWindow { Owner = this }.ShowDialog();
|
||||
|
||||
private async void OnClosing(object? sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
if (DataContext is MainViewModel viewModel)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Windows;
|
||||
using MikrotikInstaller.App.Credentials;
|
||||
using MikrotikInstaller.Core.Settings;
|
||||
using Wpf.Ui.Appearance;
|
||||
|
||||
namespace MikrotikInstaller.App;
|
||||
|
||||
/// <summary>Die geladenen Einstellungen für die ganze App, plus das Backup-Passwort aus dem Anmeldeinformationsspeicher.</summary>
|
||||
public static class UserSettings
|
||||
{
|
||||
private const string BackupPasswordId = "backup-password";
|
||||
private static readonly AppSettingsStore Store = new();
|
||||
|
||||
public static AppSettings Current { get; private set; } = Store.Load();
|
||||
|
||||
public static event EventHandler? Changed;
|
||||
|
||||
public static void Save(AppSettings settings)
|
||||
{
|
||||
Store.Save(settings);
|
||||
Current = settings;
|
||||
ApplyTheme();
|
||||
Changed?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>Leer/null = Backups unverschlüsselt.</summary>
|
||||
public static string? BackupPassword
|
||||
{
|
||||
get => WindowsCredentialStore.TryRead(BackupPasswordId) is { Length: > 0 } password ? password : null;
|
||||
}
|
||||
|
||||
/// <summary>Gibt false zurück, wenn Windows das Passwort nicht speichern konnte (Mac v1.11.4: dann nicht als gesetzt anzeigen).</summary>
|
||||
public static bool SetBackupPassword(string? password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
WindowsCredentialStore.Delete(BackupPasswordId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return WindowsCredentialStore.Save(BackupPasswordId, "backup", password);
|
||||
}
|
||||
|
||||
public static void ApplyTheme()
|
||||
{
|
||||
if (Current.Theme == AppTheme.System)
|
||||
{
|
||||
ApplicationThemeManager.ApplySystemTheme(updateAccent: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplicationThemeManager.Apply(Current.Theme == AppTheme.Dark ? ApplicationTheme.Dark : ApplicationTheme.Light, updateAccent: false);
|
||||
}
|
||||
|
||||
ApplicationAccentColorManager.Apply(App.BrandAccent, ApplicationThemeManager.GetAppTheme(), systemGlassColor: false, systemAccentColor: false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Win32;
|
||||
using MikrotikInstaller.Core.Settings;
|
||||
|
||||
namespace MikrotikInstaller.App.ViewModels;
|
||||
|
||||
/// <summary>Einstellungen-Fenster (Mac: Einstellungen → Allgemein/Darstellung). Änderungen gelten erst mit „Speichern".</summary>
|
||||
public partial class SettingsViewModel : ObservableObject
|
||||
{
|
||||
public SettingsViewModel()
|
||||
{
|
||||
var settings = UserSettings.Current;
|
||||
_theme = settings.Theme;
|
||||
_checkUpdatesOnConnect = settings.CheckUpdatesOnConnect;
|
||||
_professionalMode = settings.ProfessionalMode;
|
||||
_testEnvironment = settings.TestEnvironment;
|
||||
_backupFolder = settings.EffectiveBackupFolder;
|
||||
_hasBackupPassword = UserSettings.BackupPassword is not null;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private AppTheme _theme;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _checkUpdatesOnConnect;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _professionalMode;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _testEnvironment;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _backupFolder;
|
||||
|
||||
/// <summary>Neues Backup-Passwort; leer lassen = unverändert.</summary>
|
||||
[ObservableProperty]
|
||||
private string _newBackupPassword = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasBackupPassword;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _removeBackupPassword;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _restartIntro;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _errorMessage;
|
||||
|
||||
/// <summary>Wird nach erfolgreichem Speichern ausgelöst, damit das Fenster sich schließt.</summary>
|
||||
public event EventHandler? Saved;
|
||||
|
||||
[RelayCommand]
|
||||
private void ChooseBackupFolder()
|
||||
{
|
||||
var dialog = new OpenFolderDialog { Title = "Ordner für Backups wählen", InitialDirectory = BackupFolder };
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
BackupFolder = dialog.FolderName;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Save()
|
||||
{
|
||||
ErrorMessage = null;
|
||||
if (RemoveBackupPassword)
|
||||
{
|
||||
UserSettings.SetBackupPassword(null);
|
||||
}
|
||||
else if (NewBackupPassword.Length > 0 && !UserSettings.SetBackupPassword(NewBackupPassword))
|
||||
{
|
||||
ErrorMessage = "Das Backup-Passwort konnte nicht gespeichert werden. Neue Backups bleiben deshalb unverändert.";
|
||||
return;
|
||||
}
|
||||
|
||||
var current = UserSettings.Current;
|
||||
UserSettings.Save(current with
|
||||
{
|
||||
Theme = Theme,
|
||||
CheckUpdatesOnConnect = CheckUpdatesOnConnect,
|
||||
ProfessionalMode = ProfessionalMode,
|
||||
TestEnvironment = TestEnvironment,
|
||||
BackupFolder = BackupFolder == new AppSettings().EffectiveBackupFolder ? null : BackupFolder,
|
||||
IntroCompleted = current.IntroCompleted && !RestartIntro,
|
||||
});
|
||||
Saved?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,10 @@ public partial class DeviceOverviewStepViewModel : WizardStepViewModelBase
|
||||
// Läuft im Hintergrund weiter, ohne die Navigation zu blockieren — ein frisches Gerät
|
||||
// hat an dieser Stelle im Assistenten meist noch keinen Internetzugang, die Prüfung
|
||||
// schlägt dann einfach fehl (kein Fehlerzustand) und wird nach dem Anwenden wiederholt.
|
||||
_ = CheckForUpdatesInBackgroundAsync(_session.Client);
|
||||
if (UserSettings.Current.CheckUpdatesOnConnect)
|
||||
{
|
||||
_ = CheckForUpdatesInBackgroundAsync(_session.Client);
|
||||
}
|
||||
}
|
||||
catch (RouterOsException ex)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,8 @@ public partial class ModeSelectionStepViewModel : WizardStepViewModelBase
|
||||
[ObservableProperty]
|
||||
private bool _isExpertMode;
|
||||
|
||||
public override bool ShouldSkip => _session.SelectedArea == AppArea.BackupManagement;
|
||||
/// <summary>Ohne „Professioneller Netzwerker" gibt es nichts zu wählen — dann immer der geführte Assistent.</summary>
|
||||
public override bool ShouldSkip => _session.SelectedArea == AppArea.BackupManagement || !UserSettings.Current.ProfessionalMode;
|
||||
|
||||
public override Task OnActivatedAsync()
|
||||
{
|
||||
|
||||
@@ -27,7 +27,14 @@ public sealed class WizardSession
|
||||
/// Ob der Nutzer im Moduswahl-Schritt den Experten-Modus gewählt hat. Steuert, ob die
|
||||
/// nachfolgenden Schritte zusätzliche, technischere Felder anzeigen. Standard: Laien-Modus (false).
|
||||
/// </summary>
|
||||
public bool IsExpertMode { get; set; }
|
||||
public bool IsExpertMode
|
||||
{
|
||||
// Ohne "Professioneller Netzwerker" (Einstellungen) gibt es keinen Experten-Modus (Mac v1.3.0).
|
||||
get => _isExpertMode && UserSettings.Current.ProfessionalMode;
|
||||
set => _isExpertMode = value;
|
||||
}
|
||||
|
||||
private bool _isExpertMode;
|
||||
|
||||
/// <summary>
|
||||
/// Direkt nach dem Verbinden gewählter Bereich (siehe <see cref="Steps.AreaSelectionStepViewModel"/>).
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<ui:FluentWindow x:Class="MikrotikInstaller.App.Views.SettingsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:vm="clr-namespace:MikrotikInstaller.App.ViewModels"
|
||||
xmlns:controls="clr-namespace:MikrotikInstaller.App.Views.Controls"
|
||||
Title="Einstellungen" Width="560" Height="680"
|
||||
WindowStartupLocation="CenterOwner" ResizeMode="NoResize"
|
||||
ExtendsContentIntoTitleBar="True" WindowBackdropType="Mica">
|
||||
<Window.DataContext>
|
||||
<vm:SettingsViewModel />
|
||||
</Window.DataContext>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ui:TitleBar Grid.Row="0" Title="Einstellungen" ShowMinimize="False" ShowMaximize="False" />
|
||||
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" Padding="28,8,28,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Darstellung" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,8" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,20">
|
||||
<RadioButton Content="Wie System" GroupName="Theme" Margin="0,0,16,0"
|
||||
IsChecked="{Binding Theme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=System}" />
|
||||
<RadioButton Content="Hell" GroupName="Theme" Margin="0,0,16,0"
|
||||
IsChecked="{Binding Theme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Light}" />
|
||||
<RadioButton Content="Dunkel" GroupName="Theme"
|
||||
IsChecked="{Binding Theme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Dark}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Allgemein" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,8" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||
<CheckBox Content="Beim Verbinden nach RouterOS-Updates suchen" IsChecked="{Binding CheckUpdatesOnConnect}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||
<CheckBox Content="Professioneller Netzwerker" IsChecked="{Binding ProfessionalMode}" />
|
||||
<controls:InfoHint Text="Blendet den Experten-Modus im Einrichtungs-Assistenten ein: zusätzliche, technischere RouterOS-Einstellungen in den einzelnen Schritten. Für die meisten Haushalte nicht nötig." />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||
<CheckBox Content="Testumgebung anbieten" IsChecked="{Binding TestEnvironment}" />
|
||||
<controls:InfoHint Text="Zeigt im Verbindungs-Schritt simulierte Testgeräte, mit denen du den Assistenten ohne eigenen Router ausprobieren kannst. Änderungen bleiben nur bis zum Beenden der App erhalten." />
|
||||
</StackPanel>
|
||||
<CheckBox Content="Einführung beim nächsten Start erneut zeigen" IsChecked="{Binding RestartIntro}" Margin="0,0,0,20" />
|
||||
|
||||
<TextBlock Text="Sicherungen" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,8" />
|
||||
<TextBlock Text="Ordner für Backups auf diesem PC" Margin="0,0,0,4" />
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBox Grid.Column="0" Text="{Binding BackupFolder, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<ui:Button Grid.Column="1" Content="Wählen …" Margin="8,0,0,0" Command="{Binding ChooseBackupFolderCommand}" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
|
||||
<TextBlock Text="Backup-Passwort" />
|
||||
<controls:InfoHint Text="Mit Passwort werden neue Backups von RouterOS verschlüsselt — auf dem Gerät und als Kopie auf diesem PC. Ohne das Passwort lassen sie sich nicht mehr zurückspielen, bewahre es also gut auf." />
|
||||
</StackPanel>
|
||||
<TextBlock Text="Ein Backup-Passwort ist gesetzt. Leer lassen, um es zu behalten." FontSize="12" Margin="0,0,0,4"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Visibility="{Binding HasBackupPassword, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBlock Text="Kein Backup-Passwort gesetzt — neue Backups sind unverschlüsselt." FontSize="12" Margin="0,0,0,4"
|
||||
Foreground="{StaticResource MikrotikWarningBrush}"
|
||||
Visibility="{Binding HasBackupPassword, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter=Invert}" />
|
||||
<ui:PasswordBox Password="{Binding NewBackupPassword, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,8" />
|
||||
<CheckBox Content="Backup-Passwort entfernen" IsChecked="{Binding RemoveBackupPassword}"
|
||||
Visibility="{Binding HasBackupPassword, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<TextBlock Text="{Binding ErrorMessage}" TextWrapping="Wrap" Foreground="{StaticResource MikrotikRedBrush}" Margin="0,12,0,0"
|
||||
Visibility="{Binding ErrorMessage, Converter={StaticResource StringToVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<Border Grid.Row="2" BorderBrush="{DynamicResource DividerStrokeColorDefaultBrush}" BorderThickness="0,1,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="28,16">
|
||||
<ui:Button Content="Abbrechen" Margin="0,0,8,0" IsCancel="True" Click="OnCancel" />
|
||||
<ui:Button Content="Speichern" Appearance="Primary" IsDefault="True" Command="{Binding SaveCommand}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ui:FluentWindow>
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Windows;
|
||||
using MikrotikInstaller.App.ViewModels;
|
||||
using Wpf.Ui.Controls;
|
||||
|
||||
namespace MikrotikInstaller.App.Views;
|
||||
|
||||
public partial class SettingsWindow : FluentWindow
|
||||
{
|
||||
public SettingsWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
((SettingsViewModel)DataContext).Saved += (_, _) => Close();
|
||||
}
|
||||
|
||||
private void OnCancel(object sender, RoutedEventArgs e) => Close();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using MikrotikInstaller.Core.Settings;
|
||||
|
||||
namespace MikrotikInstaller.Core.Tests.Settings;
|
||||
|
||||
public class AppSettingsStoreTests : IDisposable
|
||||
{
|
||||
private readonly string _filePath = Path.Combine(Path.GetTempPath(), $"settings-test-{Guid.NewGuid()}.json");
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (File.Exists(_filePath))
|
||||
{
|
||||
File.Delete(_filePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_NoFile_ReturnsDefaults()
|
||||
{
|
||||
var settings = new AppSettingsStore(_filePath).Load();
|
||||
|
||||
Assert.Equal(AppTheme.System, settings.Theme);
|
||||
Assert.True(settings.TestEnvironment);
|
||||
Assert.False(settings.ProfessionalMode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveThenLoad_RoundTrips()
|
||||
{
|
||||
var store = new AppSettingsStore(_filePath);
|
||||
store.Save(new AppSettings { Theme = AppTheme.Dark, ProfessionalMode = true, BackupFolder = @"D:\Sicherungen" });
|
||||
|
||||
var loaded = store.Load();
|
||||
|
||||
Assert.Equal(AppTheme.Dark, loaded.Theme);
|
||||
Assert.True(loaded.ProfessionalMode);
|
||||
Assert.Equal(@"D:\Sicherungen", loaded.EffectiveBackupFolder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_CorruptFile_ReturnsDefaults()
|
||||
{
|
||||
File.WriteAllText(_filePath, "{ kaputt");
|
||||
|
||||
Assert.Equal(new AppSettings(), new AppSettingsStore(_filePath).Load());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MikrotikInstaller.Core.Settings;
|
||||
|
||||
public enum AppTheme
|
||||
{
|
||||
System,
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lokale Einstellungen der App (analog zu den Einstellungen der Mac-Version, ohne App-Store-Teil).
|
||||
/// Enthält bewusst kein Backup-Passwort — das liegt wie Router-Passwörter im Windows-Anmeldeinformationsspeicher.
|
||||
/// </summary>
|
||||
public sealed record AppSettings
|
||||
{
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public AppTheme Theme { get; init; } = AppTheme.System;
|
||||
|
||||
/// <summary>Beim Verbinden im Hintergrund nach einer neueren RouterOS-Version suchen.</summary>
|
||||
public bool CheckUpdatesOnConnect { get; init; } = true;
|
||||
|
||||
/// <summary>„Professioneller Netzwerker": blendet den Experten-Modus im Assistenten (und später den Experte-Bereich) ein.</summary>
|
||||
public bool ProfessionalMode { get; init; }
|
||||
|
||||
/// <summary>Simulierte Testgeräte im Verbindungs-Schritt anbieten.</summary>
|
||||
public bool TestEnvironment { get; init; } = true;
|
||||
|
||||
/// <summary>Ordner für auf den PC geladene Backups; null = Standard (Dokumente\MikroTik-Backups).</summary>
|
||||
public string? BackupFolder { get; init; }
|
||||
|
||||
/// <summary>Die Einführung beim ersten Start wurde abgeschlossen oder übersprungen.</summary>
|
||||
public bool IntroCompleted { get; init; }
|
||||
|
||||
public string EffectiveBackupFolder => BackupFolder ?? Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MikroTik-Backups");
|
||||
}
|
||||
|
||||
/// <summary>Liest/schreibt <see cref="AppSettings"/> als JSON unter %AppData%\MikroTik-Installer\settings.json.</summary>
|
||||
public sealed class AppSettingsStore
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
||||
|
||||
private readonly string _filePath;
|
||||
|
||||
/// <param name="filePath">Nur zum Überschreiben in Tests.</param>
|
||||
public AppSettingsStore(string? filePath = null)
|
||||
{
|
||||
_filePath = filePath ?? Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MikroTik-Installer", "settings.json");
|
||||
}
|
||||
|
||||
/// <summary>Fehlende oder beschädigte Datei ergibt die Standardwerte — Einstellungen blockieren nie den Start.</summary>
|
||||
public AppSettings Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
return File.Exists(_filePath)
|
||||
? JsonSerializer.Deserialize<AppSettings>(File.ReadAllText(_filePath), JsonOptions) ?? new AppSettings()
|
||||
: new AppSettings();
|
||||
}
|
||||
catch (Exception ex) when (ex is JsonException or IOException)
|
||||
{
|
||||
return new AppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(AppSettings settings)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(_filePath)!);
|
||||
File.WriteAllText(_filePath, JsonSerializer.Serialize(settings, JsonOptions));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user