From 7fa5f4a4051623151d5292bc4d34073244930734 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 11 Jan 2026 22:01:50 +0100
Subject: [PATCH 01/10] Feature: Configure backup retention
---
.../Resources/Strings.Designer.cs | 22 +++++++++++++
.../Resources/Strings.resx | 10 ++++++
.../Network/NetworkInterface.cs | 1 -
.../GlobalStaticConfiguration.cs | 16 +++++-----
.../NETworkManager.Settings/SettingsInfo.cs | 31 +++++++++++++++++++
.../SettingsManager.cs | 12 ++++++-
.../ViewModels/SettingsProfilesViewModel.cs | 24 ++++++++++++++
.../ViewModels/SettingsSettingsViewModel.cs | 30 ++++++++++++++++--
.../Views/SettingsSettingsView.xaml | 18 ++++++++++-
9 files changed, 150 insertions(+), 14 deletions(-)
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index fbd0f19ec1..4e7e0aca64 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -4552,6 +4552,19 @@ public static string HelpMessage_SaveCredentials {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die The number of settings backups that are retained before the oldest one is deleted.
+ ///
+ ///An automatic backup is only created once a day, before saving a change.
+ ///
+ ///The value 0 disables the creation of automatic backups. Backups that have already been created are not deleted. ähnelt.
+ ///
+ public static string HelpMessage_SettingsMaximumNumberOfBackups {
+ get {
+ return ResourceManager.GetString("HelpMessage_SettingsMaximumNumberOfBackups", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Displays the status bar in the bottom-left of the WebView when hovering over a link. ähnelt.
///
@@ -5783,6 +5796,15 @@ public static string MaximumHops {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die Maximum Number of Backups ähnelt.
+ ///
+ public static string MaximumNumberOfBackups {
+ get {
+ return ResourceManager.GetString("MaximumNumberOfBackups", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Maximum number ({0}) of hops/router reached! ähnelt.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 28ccf1c004..14ca461f78 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -3951,4 +3951,14 @@ If you click Cancel, the profile file will remain unencrypted.
Could not parse "{0}".
+
+ Maximum Number of Backups
+
+
+ The number of settings backups that are retained before the oldest one is deleted.
+
+An automatic backup is only created once a day, before saving a change.
+
+The value 0 disables the creation of automatic backups. Backups that have already been created are not deleted.
+
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/Network/NetworkInterface.cs b/Source/NETworkManager.Models/Network/NetworkInterface.cs
index d7662f6704..7cd8c34038 100644
--- a/Source/NETworkManager.Models/Network/NetworkInterface.cs
+++ b/Source/NETworkManager.Models/Network/NetworkInterface.cs
@@ -527,7 +527,6 @@ private static void RemoveIPAddressFromNetworkInterface(NetworkInterfaceConfig c
#endregion
-
#region Events
///
diff --git a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
index af7ea9117d..28c92928a6 100644
--- a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
+++ b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
@@ -46,9 +46,6 @@ public static class GlobalStaticConfiguration
public static string ZipFileExtensionFilter => "ZIP Archive (*.zip)|*.zip";
public static string XmlFileExtensionFilter => "XML-File (*.xml)|*.xml";
- // Backup settings
- public static int Backup_MaximumNumberOfBackups => 10;
-
#endregion
#region Default settings
@@ -74,18 +71,21 @@ public static class GlobalStaticConfiguration
public static bool Status_ShowWindowOnNetworkChange => true;
public static int Status_WindowCloseTime => 10;
- // HotKey
+ // Settings: HotKey
public static int HotKey_ShowWindowKey => 79;
public static int HotKey_ShowWindowModifier => 3;
- // Update
+ // Settings: Update
public static bool Update_CheckForUpdatesAtStartup => true;
-
public static bool Update_CheckForPreReleases => false;
-
- // Experimental
public static bool Experimental_EnableExperimentalFeatures => false;
+ // Settings: Profiles
+ public static int Profiles_MaximumNumberOfBackups => 10;
+
+ // Settings: Settings
+ public static int Settings_MaximumNumberOfBackups => 10;
+
// Application: Dashboard
public static string Dashboard_PublicIPv4Address => "1.1.1.1";
public static string Dashboard_PublicIPv6Address => "2606:4700:4700::1111";
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index 4a41e94680..b8f993cff0 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -582,6 +582,37 @@ public string Profiles_LastSelected
}
}
+ private int _profiles_MaximumNumberOfBackups = GlobalStaticConfiguration.Profiles_MaximumNumberOfBackups;
+
+ public int Profiles_MaximumNumberOfBackups
+ {
+ get => _profiles_MaximumNumberOfBackups;
+ set
+ {
+ if (value == _profiles_MaximumNumberOfBackups)
+ return;
+
+ _profiles_MaximumNumberOfBackups = value;
+ OnPropertyChanged();
+ }
+ }
+
+ // Settings
+ private int _settings_MaximumNumberOfBackups = GlobalStaticConfiguration.Settings_MaximumNumberOfBackups;
+
+ public int Settings_MaximumNumberOfBackups
+ {
+ get => _settings_MaximumNumberOfBackups;
+ set
+ {
+ if (value == _settings_MaximumNumberOfBackups)
+ return;
+
+ _settings_MaximumNumberOfBackups = value;
+ OnPropertyChanged();
+ }
+ }
+
#endregion
#region Others
diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs
index 7c0041f063..6aeb6b9a96 100644
--- a/Source/NETworkManager.Settings/SettingsManager.cs
+++ b/Source/NETworkManager.Settings/SettingsManager.cs
@@ -265,6 +265,16 @@ private static void SerializeToFile(string filePath)
/// called as part of a daily maintenance routine.
private static void CreateDailyBackupIfNeeded()
{
+ var maxBackups = Current.Settings_MaximumNumberOfBackups;
+
+ // Check if backups are disabled
+ if (maxBackups == 0)
+ {
+ Log.Debug("Daily backups are disabled. Skipping backup creation...");
+
+ return;
+ }
+
var currentDate = DateTime.Now.Date;
if (Current.LastBackup < currentDate)
@@ -284,7 +294,7 @@ private static void CreateDailyBackupIfNeeded()
// Cleanup old backups
CleanupBackups(GetSettingsBackupFolderLocation(),
GetSettingsFileName(),
- GlobalStaticConfiguration.Backup_MaximumNumberOfBackups);
+ maxBackups);
Current.LastBackup = currentDate;
}
diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
index fd42c76821..04f4b7fcb1 100644
--- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
@@ -22,6 +22,8 @@ public class SettingsProfilesViewModel : ViewModelBase
public Action CloseAction { get; set; }
+ private readonly bool _isLoading;
+
private string _location;
public string Location
@@ -67,12 +69,31 @@ public ProfileFileInfo SelectedProfileFile
}
}
+ private int _maximumNumberOfBackups;
+
+ public int MaximumNumberOfBackups
+ {
+ get => _maximumNumberOfBackups;
+ set
+ {
+ if (value == _maximumNumberOfBackups)
+ return;
+
+ if (!_isLoading)
+ SettingsManager.Current.Profiles_MaximumNumberOfBackups = value;
+
+ _maximumNumberOfBackups = value;
+ OnPropertyChanged();
+ }
+ }
#endregion
#region Constructor, LoadSettings
public SettingsProfilesViewModel()
{
+ _isLoading = true;
+
ProfileFiles = new CollectionViewSource { Source = ProfileManager.ProfileFiles }.View;
ProfileFiles.SortDescriptions.Add(
new SortDescription(nameof(ProfileFileInfo.Name), ListSortDirection.Ascending));
@@ -80,11 +101,14 @@ public SettingsProfilesViewModel()
SelectedProfileFile = ProfileFiles.Cast().FirstOrDefault();
LoadSettings();
+
+ _isLoading = false;
}
private void LoadSettings()
{
Location = ProfileManager.GetProfilesFolderLocation();
+ MaximumNumberOfBackups = SettingsManager.Current.Profiles_MaximumNumberOfBackups;
}
#endregion
diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
index 399ffb1e02..fbe8f3be3f 100644
--- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
@@ -1,8 +1,6 @@
-using MahApps.Metro.SimpleChildWindow;
-using NETworkManager.Localization.Resources;
+using NETworkManager.Localization.Resources;
using NETworkManager.Settings;
using NETworkManager.Utilities;
-using NETworkManager.Views;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
@@ -16,6 +14,8 @@ public class SettingsSettingsViewModel : ViewModelBase
#region Variables
public Action CloseAction { get; set; }
+ private readonly bool _isLoading;
+
private string _location;
public string Location
@@ -30,18 +30,42 @@ public string Location
OnPropertyChanged();
}
}
+
+ private int _maximumNumberOfBackups;
+
+ public int MaximumNumberOfBackups
+ {
+ get => _maximumNumberOfBackups;
+ set
+ {
+ if (value == _maximumNumberOfBackups)
+ return;
+
+ if (!_isLoading)
+ SettingsManager.Current.Settings_MaximumNumberOfBackups = value;
+
+ _maximumNumberOfBackups = value;
+ OnPropertyChanged();
+ }
+ }
+
#endregion
#region Constructor, LoadSettings
public SettingsSettingsViewModel()
{
+ _isLoading = true;
+
LoadSettings();
+
+ _isLoading = false;
}
private void LoadSettings()
{
Location = SettingsManager.GetSettingsFolderLocation();
+ MaximumNumberOfBackups = SettingsManager.Current.Settings_MaximumNumberOfBackups;
}
#endregion
diff --git a/Source/NETworkManager/Views/SettingsSettingsView.xaml b/Source/NETworkManager/Views/SettingsSettingsView.xaml
index 7f91995927..c97e846d8a 100644
--- a/Source/NETworkManager/Views/SettingsSettingsView.xaml
+++ b/Source/NETworkManager/Views/SettingsSettingsView.xaml
@@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:viewModels="clr-namespace:NETworkManager.ViewModels"
xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization"
@@ -30,8 +31,23 @@
+
+
+
+
+
+
+
+
+
+
+ Style="{StaticResource DefaultButton}" HorizontalAlignment="Left"
+ />
\ No newline at end of file
From 4b0f349d3bd682513712f4b7145218bf6693472b Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 11 Jan 2026 22:32:49 +0100
Subject: [PATCH 02/10] Feature: Daily Backup
---
.../Resources/Strings.Designer.cs | 33 ++++++++-------
.../Resources/Strings.resx | 13 +++---
.../GlobalStaticConfiguration.cs | 2 +
.../NETworkManager.Settings/SettingsInfo.cs | 30 +++++++++++++
.../SettingsManager.cs | 6 +--
.../ViewModels/SettingsProfilesViewModel.cs | 19 +++++++++
.../ViewModels/SettingsSettingsViewModel.cs | 19 +++++++++
.../Views/SettingsProfilesView.xaml | 24 ++++++++++-
.../Views/SettingsSettingsView.xaml | 16 ++++---
Website/docs/changelog/next-release.md | 2 +-
Website/docs/settings/profiles.md | 42 +++++++++++++++++--
Website/docs/settings/settings.md | 32 ++++++++++++--
12 files changed, 197 insertions(+), 41 deletions(-)
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index 4e7e0aca64..b57b12bff7 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -2220,6 +2220,15 @@ public static string Country {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die Create daily backup ähnelt.
+ ///
+ public static string CreateDailyBackup {
+ get {
+ return ResourceManager.GetString("CreateDailyBackup", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Credential ähnelt.
///
@@ -4450,6 +4459,15 @@ public static string HelpMessage_ExperimentalFeatures {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die Number of backups that are retained before the oldest one is deleted. ähnelt.
+ ///
+ public static string HelpMessage_MaximumNumberOfBackups {
+ get {
+ return ResourceManager.GetString("HelpMessage_MaximumNumberOfBackups", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Application that is displayed at startup. ähnelt.
///
@@ -4552,19 +4570,6 @@ public static string HelpMessage_SaveCredentials {
}
}
- ///
- /// Sucht eine lokalisierte Zeichenfolge, die The number of settings backups that are retained before the oldest one is deleted.
- ///
- ///An automatic backup is only created once a day, before saving a change.
- ///
- ///The value 0 disables the creation of automatic backups. Backups that have already been created are not deleted. ähnelt.
- ///
- public static string HelpMessage_SettingsMaximumNumberOfBackups {
- get {
- return ResourceManager.GetString("HelpMessage_SettingsMaximumNumberOfBackups", resourceCulture);
- }
- }
-
///
/// Sucht eine lokalisierte Zeichenfolge, die Displays the status bar in the bottom-left of the WebView when hovering over a link. ähnelt.
///
@@ -5797,7 +5802,7 @@ public static string MaximumHops {
}
///
- /// Sucht eine lokalisierte Zeichenfolge, die Maximum Number of Backups ähnelt.
+ /// Sucht eine lokalisierte Zeichenfolge, die Maximum number of backups ähnelt.
///
public static string MaximumNumberOfBackups {
get {
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 14ca461f78..3dd4967eaf 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -3952,13 +3952,12 @@ If you click Cancel, the profile file will remain unencrypted.
Could not parse "{0}".
- Maximum Number of Backups
+ Maximum number of backups
-
- The number of settings backups that are retained before the oldest one is deleted.
-
-An automatic backup is only created once a day, before saving a change.
-
-The value 0 disables the creation of automatic backups. Backups that have already been created are not deleted.
+
+ Create daily backup
+
+
+ Number of backups that are retained before the oldest one is deleted.
\ No newline at end of file
diff --git a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
index 28c92928a6..851a5c233c 100644
--- a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
+++ b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
@@ -81,9 +81,11 @@ public static class GlobalStaticConfiguration
public static bool Experimental_EnableExperimentalFeatures => false;
// Settings: Profiles
+ public static bool Profiles_IsDailyBackupEnabled => true;
public static int Profiles_MaximumNumberOfBackups => 10;
// Settings: Settings
+ public static bool Settings_IsDailyBackupEnabled => true;
public static int Settings_MaximumNumberOfBackups => 10;
// Application: Dashboard
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index b8f993cff0..97834d7515 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -582,6 +582,21 @@ public string Profiles_LastSelected
}
}
+ private bool _profiles_IsDailyBackupEnabled = GlobalStaticConfiguration.Profiles_IsDailyBackupEnabled;
+
+ public bool Profiles_IsDailyBackupEnabled
+ {
+ get => _profiles_IsDailyBackupEnabled;
+ set
+ {
+ if (value == _profiles_IsDailyBackupEnabled)
+ return;
+
+ _profiles_IsDailyBackupEnabled = value;
+ OnPropertyChanged();
+ }
+ }
+
private int _profiles_MaximumNumberOfBackups = GlobalStaticConfiguration.Profiles_MaximumNumberOfBackups;
public int Profiles_MaximumNumberOfBackups
@@ -598,6 +613,21 @@ public int Profiles_MaximumNumberOfBackups
}
// Settings
+ private bool _settings_IsDailyBackupEnabled = GlobalStaticConfiguration.Settings_IsDailyBackupEnabled;
+
+ public bool Settings_IsDailyBackupEnabled
+ {
+ get => _settings_IsDailyBackupEnabled;
+ set
+ {
+ if (value == _settings_IsDailyBackupEnabled)
+ return;
+
+ _settings_IsDailyBackupEnabled = value;
+ OnPropertyChanged();
+ }
+ }
+
private int _settings_MaximumNumberOfBackups = GlobalStaticConfiguration.Settings_MaximumNumberOfBackups;
public int Settings_MaximumNumberOfBackups
diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs
index 6aeb6b9a96..a0c5c9c1ca 100644
--- a/Source/NETworkManager.Settings/SettingsManager.cs
+++ b/Source/NETworkManager.Settings/SettingsManager.cs
@@ -265,10 +265,8 @@ private static void SerializeToFile(string filePath)
/// called as part of a daily maintenance routine.
private static void CreateDailyBackupIfNeeded()
{
- var maxBackups = Current.Settings_MaximumNumberOfBackups;
-
// Check if backups are disabled
- if (maxBackups == 0)
+ if (!Current.Settings_IsDailyBackupEnabled)
{
Log.Debug("Daily backups are disabled. Skipping backup creation...");
@@ -294,7 +292,7 @@ private static void CreateDailyBackupIfNeeded()
// Cleanup old backups
CleanupBackups(GetSettingsBackupFolderLocation(),
GetSettingsFileName(),
- maxBackups);
+ Current.Settings_MaximumNumberOfBackups);
Current.LastBackup = currentDate;
}
diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
index 04f4b7fcb1..7dd0e17546 100644
--- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
@@ -69,6 +69,24 @@ public ProfileFileInfo SelectedProfileFile
}
}
+ private bool _isDailyBackupEnabled;
+
+ public bool IsDailyBackupEnabled
+ {
+ get => _isDailyBackupEnabled;
+ set
+ {
+ if (value == _isDailyBackupEnabled)
+ return;
+
+ if (!_isLoading)
+ SettingsManager.Current.Profiles_IsDailyBackupEnabled = value;
+
+ _isDailyBackupEnabled = value;
+ OnPropertyChanged();
+ }
+ }
+
private int _maximumNumberOfBackups;
public int MaximumNumberOfBackups
@@ -108,6 +126,7 @@ public SettingsProfilesViewModel()
private void LoadSettings()
{
Location = ProfileManager.GetProfilesFolderLocation();
+ IsDailyBackupEnabled = SettingsManager.Current.Profiles_IsDailyBackupEnabled;
MaximumNumberOfBackups = SettingsManager.Current.Profiles_MaximumNumberOfBackups;
}
diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
index fbe8f3be3f..dc1d7d95b1 100644
--- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
@@ -31,6 +31,24 @@ public string Location
}
}
+ private bool _isDailyBackupEnabled;
+
+ public bool IsDailyBackupEnabled
+ {
+ get => _isDailyBackupEnabled;
+ set
+ {
+ if (value == _isDailyBackupEnabled)
+ return;
+
+ if (!_isLoading)
+ SettingsManager.Current.Settings_IsDailyBackupEnabled = value;
+
+ _isDailyBackupEnabled = value;
+ OnPropertyChanged();
+ }
+ }
+
private int _maximumNumberOfBackups;
public int MaximumNumberOfBackups
@@ -65,6 +83,7 @@ public SettingsSettingsViewModel()
private void LoadSettings()
{
Location = SettingsManager.GetSettingsFolderLocation();
+ IsDailyBackupEnabled = SettingsManager.Current.Settings_IsDailyBackupEnabled;
MaximumNumberOfBackups = SettingsManager.Current.Settings_MaximumNumberOfBackups;
}
diff --git a/Source/NETworkManager/Views/SettingsProfilesView.xaml b/Source/NETworkManager/Views/SettingsProfilesView.xaml
index d73f2640c8..0989c4a24d 100644
--- a/Source/NETworkManager/Views/SettingsProfilesView.xaml
+++ b/Source/NETworkManager/Views/SettingsProfilesView.xaml
@@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:converters="clr-namespace:NETworkManager.Converters;assembly=NETworkManager.Converters"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:wpfHelpers="clr-namespace:NETworkManager.Utilities.WPF;assembly=NETworkManager.Utilities.WPF"
@@ -144,7 +145,8 @@
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/Source/NETworkManager/Views/SettingsSettingsView.xaml b/Source/NETworkManager/Views/SettingsSettingsView.xaml
index c97e846d8a..c4880b397e 100644
--- a/Source/NETworkManager/Views/SettingsSettingsView.xaml
+++ b/Source/NETworkManager/Views/SettingsSettingsView.xaml
@@ -32,13 +32,17 @@
-
-
-
+
+
+
+
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index 28bae845be..bf65f5504a 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -65,7 +65,7 @@ Release date: **xx.xx.2025**
**Settings**
- Settings file format migrated from `XML` to `JSON`. The settings file will be automatically converted on first load after the update. [#3282](https://github.com/BornToBeRoot/NETworkManager/pull/3282)
-- Create a daily backup of the settings file before saving changes. Up to `10` backup files are kept in the `Backups` subfolder of the settings directory. [#3283](https://github.com/BornToBeRoot/NETworkManager/pull/3283)
+- Create a daily backup of the settings file before saving changes. By default, up to `10` backup files are kept in the `Backups` subfolder of the settings directory. [#3283](https://github.com/BornToBeRoot/NETworkManager/pull/3283) [#3302](https://github.com/BornToBeRoot/NETworkManager/pull/3302)
**Dashboard**
diff --git a/Website/docs/settings/profiles.md b/Website/docs/settings/profiles.md
index 5789f625fb..186924aa4b 100644
--- a/Website/docs/settings/profiles.md
+++ b/Website/docs/settings/profiles.md
@@ -19,9 +19,21 @@ Folder where the application profiles are stored.
:::note
-It is recommended to backup the above files on a regular basis.
-
-To restore the profiles, close the application and copy the files from the backup to the above location.
+**Recommendation**
+It is strongly recommended to regularly back up your profile files.
+
+**Automatic backups**
+NETworkManager automatically creates a backup of the profile file before applying any changes. See [Create daily backup](#create-daily-backup) and [Maximum number of backups](#maximum-number-of-backups) for configuration options.
+- Location: `Profiles\Backups` subfolder (relative to the main configuration directory)
+- Naming: timestamped (e.g. `yyyyMMddHHmmss_.json`)
+- Frequency: **once per day** at most (even if multiple changes occur)
+- Retention: keeps the **10 most recent backups** (default)
+
+**Restoring profiles**
+1. Completely close NETworkManager
+2. Locate the desired backup in `Profiles\Backups`
+3. Copy the file(s) back to the original folder (overwrite existing files or copy them under a different name)
+4. Restart the application
:::
@@ -44,3 +56,27 @@ Profile files can be encrypted with a master password. See [FAQ > How to enable
At least one profile is required and must exist.
:::
+
+### Create daily backup
+
+Create a daily backup of the profile file before applying any changes.
+
+**Type**: `Boolean`
+
+**Default:** `Enabled`
+
+:::note
+
+Backups are stored in the `Profiles\Backups` subfolder. See [Location](#location) for more details.
+
+Backups are created at most once per day, even if multiple changes occur.
+
+:::
+
+### Maximum number of backups
+
+Maximum number of backups to keep. Older backups will be deleted automatically once a new backup is created.
+
+**Type:** `Integer` [Min `1`, Max `365`]
+
+**Default:** `10`
diff --git a/Website/docs/settings/settings.md b/Website/docs/settings/settings.md
index f772085232..32342b3d6e 100644
--- a/Website/docs/settings/settings.md
+++ b/Website/docs/settings/settings.md
@@ -23,20 +23,44 @@ Folder where the application settings are stored.
It is strongly recommended to regularly back up your settings files.
**Automatic backups**
-NETworkManager automatically creates a backup of the settings files before applying any changes.
-- Location: `Settings\Backups` subfolder (relative to the main configuration directory)
+NETworkManager automatically creates a backup of the settings files before applying any changes. See [Create daily backup](#create-daily-backup) and [Maximum number of backups](#maximum-number-of-backups) for configuration options.
+- Location: `Settings\Backups` subfolder
- Naming: timestamped (e.g. `yyyyMMddHHmmss_Settings.json`)
- Frequency: **once per day** at most (even if multiple changes occur)
-- Retention: keeps the **10 most recent backups**
+- Retention: keeps the **10 most recent backups** (default)
**Restoring settings**
1. Completely close NETworkManager
2. Locate the desired backup in `Settings\Backups`
-3. Copy the file(s) back to the original configuration folder (overwriting existing files)
+3. Copy the file(s) back to the original folder (overwriting existing files)
4. Restart the application
:::
+### Create daily backup
+
+Create a daily backup of the application settings before applying any changes.
+
+**Type**: `Boolean`
+
+**Default:** `Enabled`
+
+:::note
+
+Backups are stored in the `Settings\Backups` subfolder. See [Location](#location) for more details.
+
+Backups are created at most once per day, even if multiple changes occur.
+
+:::
+
+### Maximum number of backups
+
+Maximum number of backups to keep. Older backups will be deleted automatically once a new backup is created.
+
+**Type:** `Integer` [Min `1`, Max `365`]
+
+**Default:** `10`
+
### Reset
Button to reset all application settings to their default values.
From 88928b298669af8a25a30c0b0666c186d78c8787 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 11 Jan 2026 23:01:01 +0100
Subject: [PATCH 03/10] Feature: Add more backups before important operations
---
.../NETworkManager.Profiles/ProfileManager.cs | 60 +++++++++++++------
1 file changed, 42 insertions(+), 18 deletions(-)
diff --git a/Source/NETworkManager.Profiles/ProfileManager.cs b/Source/NETworkManager.Profiles/ProfileManager.cs
index e7cdb73390..c236eab1ac 100644
--- a/Source/NETworkManager.Profiles/ProfileManager.cs
+++ b/Source/NETworkManager.Profiles/ProfileManager.cs
@@ -25,14 +25,14 @@ public static class ProfileManager
private const string ProfilesFolderName = "Profiles";
///
- /// Default profile name.
+ /// Profiles backups directory name.
///
- private const string ProfilesDefaultFileName = "Default";
+ private static string BackupFolderName => "Backups";
///
- /// Profiles backups directory name.
+ /// Default profile name.
///
- private static string BackupFolderName => "Backups";
+ private const string ProfilesDefaultFileName = "Default";
///
/// Profile file extension.
@@ -290,14 +290,21 @@ public static void CreateEmptyProfileFile(string profileName)
/// New of the profile file.
public static void RenameProfileFile(ProfileFileInfo profileFileInfo, string newProfileName)
{
+ // Check if the profile is currently in use
var switchProfile = false;
-
+
if (LoadedProfileFile != null && LoadedProfileFile.Equals(profileFileInfo))
{
Save();
switchProfile = true;
}
+ // Create backup
+ Backup(profileFileInfo.Path,
+ GetProfilesBackupFolderLocation(),
+ TimestampHelper.GetTimestampFilename(Path.GetFileName(profileFileInfo.Path)));
+
+ // Create new profile info with the new name
ProfileFileInfo newProfileFileInfo = new(newProfileName,
Path.Combine(GetProfilesFolderLocation(), $"{newProfileName}{Path.GetExtension(profileFileInfo.Path)}"),
profileFileInfo.IsEncrypted)
@@ -306,15 +313,18 @@ public static void RenameProfileFile(ProfileFileInfo profileFileInfo, string new
IsPasswordValid = profileFileInfo.IsPasswordValid
};
+ // Copy the profile file to the new location
File.Copy(profileFileInfo.Path, newProfileFileInfo.Path);
ProfileFiles.Add(newProfileFileInfo);
+ // Switch profile, if it was previously loaded
if (switchProfile)
{
Switch(newProfileFileInfo, false);
LoadedProfileFileChanged(LoadedProfileFile, true);
}
+ // Remove the old profile file
File.Delete(profileFileInfo.Path);
ProfileFiles.Remove(profileFileInfo);
}
@@ -353,6 +363,11 @@ public static void EnableEncryption(ProfileFileInfo profileFileInfo, SecureStrin
switchProfile = true;
}
+ // Create backup
+ Backup(profileFileInfo.Path,
+ GetProfilesBackupFolderLocation(),
+ TimestampHelper.GetTimestampFilename(Path.GetFileName(profileFileInfo.Path)));
+
// Create a new profile info with the encryption infos
var newProfileFileInfo = new ProfileFileInfo(profileFileInfo.Name,
Path.ChangeExtension(profileFileInfo.Path, ProfileFileExtensionEncrypted), true)
@@ -361,9 +376,9 @@ public static void EnableEncryption(ProfileFileInfo profileFileInfo, SecureStrin
IsPasswordValid = true
};
- List profiles = Path.GetExtension(profileFileInfo.Path) == LegacyProfileFileExtension
- ? DeserializeFromXmlFile(profileFileInfo.Path)
- : DeserializeFromFile(profileFileInfo.Path);
+ List profiles = Path.GetExtension(profileFileInfo.Path) == LegacyProfileFileExtension ?
+ DeserializeFromXmlFile(profileFileInfo.Path) :
+ DeserializeFromFile(profileFileInfo.Path);
// Save the encrypted file
var decryptedBytes = SerializeToByteArray(profiles);
@@ -409,7 +424,12 @@ public static void ChangeMasterPassword(ProfileFileInfo profileFileInfo, SecureS
switchProfile = true;
}
- // Create a new profile info with the encryption infos
+ // Create backup
+ Backup(profileFileInfo.Path,
+ GetProfilesBackupFolderLocation(),
+ TimestampHelper.GetTimestampFilename(Path.GetFileName(profileFileInfo.Path)));
+
+ // Create new profile info with the encryption infos
var newProfileFileInfo = new ProfileFileInfo(profileFileInfo.Name,
Path.ChangeExtension(profileFileInfo.Path, ProfileFileExtensionEncrypted), true)
{
@@ -423,11 +443,9 @@ public static void ChangeMasterPassword(ProfileFileInfo profileFileInfo, SecureS
GlobalStaticConfiguration.Profile_EncryptionKeySize,
GlobalStaticConfiguration.Profile_EncryptionIterations);
- List profiles;
-
- profiles = IsXmlContent(decryptedBytes)
- ? DeserializeFromXmlByteArray(decryptedBytes)
- : DeserializeFromByteArray(decryptedBytes);
+ List profiles = IsXmlContent(decryptedBytes) ?
+ DeserializeFromXmlByteArray(decryptedBytes) :
+ DeserializeFromByteArray(decryptedBytes);
// Save the encrypted file
decryptedBytes = SerializeToByteArray(profiles);
@@ -468,7 +486,12 @@ public static void DisableEncryption(ProfileFileInfo profileFileInfo, SecureStri
switchProfile = true;
}
- // Create a new profile info
+ // Create backup
+ Backup(profileFileInfo.Path,
+ GetProfilesBackupFolderLocation(),
+ TimestampHelper.GetTimestampFilename(Path.GetFileName(profileFileInfo.Path)));
+
+ // Create new profile info
var newProfileFileInfo = new ProfileFileInfo(profileFileInfo.Name,
Path.ChangeExtension(profileFileInfo.Path, ProfileFileExtension));
@@ -478,9 +501,10 @@ public static void DisableEncryption(ProfileFileInfo profileFileInfo, SecureStri
GlobalStaticConfiguration.Profile_EncryptionKeySize,
GlobalStaticConfiguration.Profile_EncryptionIterations);
- List profiles = IsXmlContent(decryptedBytes)
- ? DeserializeFromXmlByteArray(decryptedBytes)
- : DeserializeFromByteArray(decryptedBytes);
+ List profiles = IsXmlContent(decryptedBytes) ?
+ DeserializeFromXmlByteArray(decryptedBytes) :
+ DeserializeFromByteArray(decryptedBytes);
+
// Save the decrypted profiles to the profile file
SerializeToFile(newProfileFileInfo.Path, profiles);
From 9b4ed37ce8eb6f41b365f6fc11ae07f7116971f4 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Tue, 27 Jan 2026 01:47:39 +0100
Subject: [PATCH 04/10] Feature: Create daily backup
---
Source/NETworkManager.Profiles/GroupInfo.cs | 2 +-
.../GroupInfoSerializable.cs | 1 +
.../ProfileFileData.cs | 82 ++++++++
.../NETworkManager.Profiles/ProfileManager.cs | 190 ++++++++++++++++--
.../NETworkManager.Settings/SettingsInfo.cs | 2 +-
.../SettingsManager.cs | 47 +++--
.../TimestampHelper.cs | 2 +-
Source/NETworkManager/App.xaml.cs | 15 +-
8 files changed, 299 insertions(+), 42 deletions(-)
create mode 100644 Source/NETworkManager.Profiles/ProfileFileData.cs
diff --git a/Source/NETworkManager.Profiles/GroupInfo.cs b/Source/NETworkManager.Profiles/GroupInfo.cs
index 4b39c78043..2598a48d05 100644
--- a/Source/NETworkManager.Profiles/GroupInfo.cs
+++ b/Source/NETworkManager.Profiles/GroupInfo.cs
@@ -311,4 +311,4 @@ public GroupInfo(GroupInfo group) : this(group.Name)
[XmlIgnore] public SecureString SNMP_Auth { get; set; }
public SNMPV3PrivacyProvider SNMP_PrivacyProvider { get; set; } = GlobalStaticConfiguration.SNMP_PrivacyProvider;
[XmlIgnore] public SecureString SNMP_Priv { get; set; }
-}
\ No newline at end of file
+}
diff --git a/Source/NETworkManager.Profiles/GroupInfoSerializable.cs b/Source/NETworkManager.Profiles/GroupInfoSerializable.cs
index 2ef946500c..01e9a03aa1 100644
--- a/Source/NETworkManager.Profiles/GroupInfoSerializable.cs
+++ b/Source/NETworkManager.Profiles/GroupInfoSerializable.cs
@@ -10,6 +10,7 @@ public GroupInfoSerializable()
public GroupInfoSerializable(GroupInfo profileGroup) : base(profileGroup)
{
+
}
///
diff --git a/Source/NETworkManager.Profiles/ProfileFileData.cs b/Source/NETworkManager.Profiles/ProfileFileData.cs
new file mode 100644
index 0000000000..db2093e901
--- /dev/null
+++ b/Source/NETworkManager.Profiles/ProfileFileData.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization;
+
+namespace NETworkManager.Profiles;
+
+///
+/// Represents the data structure for a profile file, including versioning, backup information, and groups of profiles.
+///
+/// This class supports property change notification through the
+/// interface, allowing consumers to track changes to its properties. The property
+/// indicates whether the data has been modified since it was last saved, but is not persisted when serializing the
+/// object. Use this class to manage and persist user profile data, including handling schema migrations via the property.
+public class ProfileFileData : INotifyPropertyChanged
+{
+ ///
+ /// Occurs when a property value changes.
+ ///
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Helper method to raise the event and mark data as changed.
+ ///
+ /// Name of the property that changed.
+ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ ProfilesChanged = true;
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ ///
+ /// Indicates if the profile file data has been modified and needs to be saved.
+ /// This is not serialized to the file.
+ ///
+ [JsonIgnore]
+ public bool ProfilesChanged { get; set; }
+
+ private int _version = 1;
+
+ ///
+ /// Schema version for handling future migrations.
+ ///
+ public int Version
+ {
+ get => _version;
+ set
+ {
+ if (value == _version)
+ return;
+
+ _version = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private DateTime? _lastBackup;
+
+ ///
+ /// Date of the last backup (used for daily backup tracking).
+ ///
+ public DateTime? LastBackup
+ {
+ get => _lastBackup;
+ set
+ {
+ if (value == _lastBackup)
+ return;
+
+ _lastBackup = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// List of groups containing profiles.
+ ///
+ public List Groups { get; set; } = [];
+}
diff --git a/Source/NETworkManager.Profiles/ProfileManager.cs b/Source/NETworkManager.Profiles/ProfileManager.cs
index c236eab1ac..e540c02369 100644
--- a/Source/NETworkManager.Profiles/ProfileManager.cs
+++ b/Source/NETworkManager.Profiles/ProfileManager.cs
@@ -92,14 +92,31 @@ private set
}
///
- /// Currently loaded groups with profiles.
+ /// Currently loaded profile file data (wrapper containing groups and metadata).
+ /// This is updated during load/save operations.
///
- public static List Groups { get; set; } = [];
+ private static ProfileFileData _loadedProfileFileData = new();
///
- /// Indicates if profiles have changed.
+ /// Currently loaded profile file data (wrapper containing groups and metadata).
+ /// This is updated during load/save operations.
///
- public static bool ProfilesChanged { get; set; }
+ public static ProfileFileData LoadedProfileFileData
+ {
+ get => _loadedProfileFileData;
+ private set
+ {
+ if (Equals(value, _loadedProfileFileData))
+ return;
+
+ _loadedProfileFileData = value;
+ }
+ }
+
+ ///
+ /// Currently loaded groups with profiles (working copy in memory).
+ ///
+ public static List Groups { get; set; } = [];
#endregion
@@ -172,9 +189,9 @@ private static void ProfileMigrationCompleted()
///
/// Method to fire the .
///
- private static void ProfilesUpdated()
+ private static void ProfilesUpdated(bool profilesChanged = true)
{
- ProfilesChanged = true;
+ LoadedProfileFileData?.ProfilesChanged = profilesChanged;
OnProfilesUpdated?.Invoke(null, EventArgs.Empty);
}
@@ -292,7 +309,7 @@ public static void RenameProfileFile(ProfileFileInfo profileFileInfo, string new
{
// Check if the profile is currently in use
var switchProfile = false;
-
+
if (LoadedProfileFile != null && LoadedProfileFile.Equals(profileFileInfo))
{
Save();
@@ -582,7 +599,7 @@ private static void Load(ProfileFileInfo profileFileInfo)
groups = DeserializeFromByteArray(decryptedBytes);
}
- AddGroups(groups);
+ AddGroups(groups, false);
// Password is valid
ProfileFiles.FirstOrDefault(x => x.Equals(profileFileInfo))!.IsPasswordValid = true;
@@ -604,8 +621,6 @@ private static void Load(ProfileFileInfo profileFileInfo)
// Load from legacy XML file
groups = DeserializeFromXmlFile(profileFileInfo.Path);
- ProfilesChanged = false;
-
LoadedProfileFile = profileFileInfo;
// Create a backup of the legacy XML file and delete the original
@@ -646,7 +661,7 @@ private static void Load(ProfileFileInfo profileFileInfo)
groups = DeserializeFromFile(profileFileInfo.Path);
}
- AddGroups(groups);
+ AddGroups(groups, false);
}
}
else
@@ -656,8 +671,6 @@ private static void Load(ProfileFileInfo profileFileInfo)
throw new FileNotFoundException($"{profileFileInfo.Path} could not be found!");
}
- ProfilesChanged = false;
-
LoadedProfileFile = profileFileInfo;
if (loadedProfileUpdated)
@@ -671,7 +684,7 @@ public static void Save()
{
if (LoadedProfileFile == null)
{
- Log.Warn("Cannot save profiles because no profile file is loaded. The profile file may be encrypted and not yet unlocked.");
+ Log.Warn("Cannot save profiles because no profile file is loaded or the profile file is encrypted and not yet unlocked.");
return;
}
@@ -679,6 +692,9 @@ public static void Save()
// Ensure the profiles directory exists.
Directory.CreateDirectory(GetProfilesFolderLocation());
+ // Create backup before modifying
+ CreateDailyBackupIfNeeded();
+
// Write profiles to the profile file (JSON, optionally encrypted).
if (LoadedProfileFile.IsEncrypted)
{
@@ -699,7 +715,7 @@ public static void Save()
SerializeToFile(LoadedProfileFile.Path, [.. Groups]);
}
- ProfilesChanged = false;
+ LoadedProfileFileData?.ProfilesChanged = false;
}
///
@@ -708,10 +724,11 @@ public static void Save()
/// Save loaded profile file (default is true)
public static void Unload(bool saveLoadedProfiles = true)
{
- if (saveLoadedProfiles && LoadedProfileFile != null && ProfilesChanged)
+ if (saveLoadedProfiles && LoadedProfileFile != null && LoadedProfileFileData?.ProfilesChanged == true)
Save();
LoadedProfileFile = null;
+ LoadedProfileFileData = null;
Groups.Clear();
@@ -741,7 +758,13 @@ public static void Switch(ProfileFileInfo info, bool saveLoadedProfiles = true)
/// List of the groups as to serialize.
private static void SerializeToFile(string filePath, List groups)
{
- var jsonString = JsonSerializer.Serialize(SerializeGroup(groups), JsonOptions);
+ // Ensure LoadedProfileFileData exists
+ LoadedProfileFileData ??= new ProfileFileData();
+
+ // Update LoadedProfileFileData with current groups
+ LoadedProfileFileData.Groups = SerializeGroup(groups);
+
+ var jsonString = JsonSerializer.Serialize(LoadedProfileFileData, JsonOptions);
File.WriteAllText(filePath, jsonString);
}
@@ -753,7 +776,13 @@ private static void SerializeToFile(string filePath, List groups)
/// Serialized list of groups as as byte array.
private static byte[] SerializeToByteArray(List groups)
{
- var jsonString = JsonSerializer.Serialize(SerializeGroup(groups), JsonOptions);
+ // Ensure LoadedProfileFileData exists
+ LoadedProfileFileData ??= new ProfileFileData();
+
+ // Update LoadedProfileFileData with current groups
+ LoadedProfileFileData.Groups = SerializeGroup(groups);
+
+ var jsonString = JsonSerializer.Serialize(LoadedProfileFileData, JsonOptions);
return Encoding.UTF8.GetBytes(jsonString);
}
@@ -873,11 +902,36 @@ private static List DeserializeFromXmlByteArray(byte[] xml)
/// List of groups as .
private static List DeserializeFromJson(string jsonString)
{
+ try
+ {
+ var profileFileData = JsonSerializer.Deserialize(jsonString, JsonOptions);
+
+ if (profileFileData?.Groups != null)
+ {
+ LoadedProfileFileData = profileFileData;
+
+ return DeserializeGroup(profileFileData.Groups);
+ }
+ }
+ catch (JsonException)
+ {
+ Log.Info("Failed to deserialize as ProfileFileData, trying legacy format (direct Groups array)...");
+ }
+
+ // Fallback: Try to deserialize as legacy format (direct array of GroupInfoSerializable)
var groupsSerializable = JsonSerializer.Deserialize>(jsonString, JsonOptions);
if (groupsSerializable == null)
throw new InvalidOperationException("Failed to deserialize JSON profile file.");
+ // Create ProfileFileData wrapper for legacy format
+ LoadedProfileFileData = new ProfileFileData
+ {
+ Groups = groupsSerializable
+ };
+
+ Log.Info("Successfully loaded profile file in legacy format. It will be migrated to new format on next save.");
+
return DeserializeGroup(groupsSerializable);
}
@@ -997,23 +1051,24 @@ private static List DeserializeGroup(List grou
/// Method to add a list of to the list.
///
/// List of groups as to add.
- private static void AddGroups(List groups)
+ private static void AddGroups(List groups, bool profilesChanged = true)
{
foreach (var group in groups)
Groups.Add(group);
- ProfilesUpdated();
+ ProfilesUpdated(profilesChanged);
}
///
/// Method to add a to the list.
///
/// Group as to add.
- public static void AddGroup(GroupInfo group)
+ public static void AddGroup(GroupInfo group, bool profilesChanged = true)
+
{
Groups.Add(group);
- ProfilesUpdated();
+ ProfilesUpdated(profilesChanged);
}
///
@@ -1143,6 +1198,97 @@ public static void RemoveProfiles(IEnumerable profiles)
#region Backup
+ ///
+ /// Creates a backup of the currently loaded profile file if a backup has not already been created for the current day.
+ ///
+ private static void CreateDailyBackupIfNeeded()
+ {
+ // Skip if daily backups are disabled
+ if (!SettingsManager.Current.Profiles_IsDailyBackupEnabled)
+ {
+ Log.Info("Daily profile backups are disabled. Skipping backup creation...");
+ return;
+ }
+
+ // Skip if no profile is loaded
+ if (LoadedProfileFile == null || LoadedProfileFileData == null)
+ {
+ Log.Info("No profile file is currently loaded. Skipping backup creation...");
+ return;
+ }
+
+ // Skip if the profile file doesn't exist yet
+ if (!File.Exists(LoadedProfileFile.Path))
+ {
+ Log.Warn($"Profile file does not exist yet: {LoadedProfileFile.Path}. Skipping backup creation...");
+ return;
+ }
+
+ // Create backup if needed
+ var currentDate = DateTime.Now.Date;
+ var lastBackupDate = LoadedProfileFileData.LastBackup?.Date ?? DateTime.MinValue;
+ var profileFileName = Path.GetFileName(LoadedProfileFile.Path);
+
+ if (lastBackupDate < currentDate)
+ {
+ Log.Info($"Creating daily backup for profile: {profileFileName}");
+
+ // Create backup
+ Backup(LoadedProfileFile.Path,
+ GetProfilesBackupFolderLocation(),
+ TimestampHelper.GetTimestampFilename(profileFileName));
+
+ // Cleanup old backups
+ CleanupBackups(GetProfilesBackupFolderLocation(),
+ profileFileName,
+ SettingsManager.Current.Profiles_MaximumNumberOfBackups);
+
+ LoadedProfileFileData.LastBackup = currentDate;
+ }
+ }
+
+ ///
+ /// Deletes older backup files in the specified folder to ensure that only the most recent backups, up to the
+ /// specified maximum, are retained.
+ ///
+ /// The full path to the directory containing the backup files to be managed.
+ /// The profile file name pattern used to identify backup files for cleanup.
+ /// The maximum number of backup files to retain. Must be greater than zero.
+ private static void CleanupBackups(string backupFolderPath, string profileFileName, int maxBackupFiles)
+ {
+ // Extract profile name without extension to match all backup files regardless of extension
+ // (e.g., "Default" matches "2025-01-19_Default.json", "2025-01-19_Default.encrypted", etc.)
+ var profileNameWithoutExtension = Path.GetFileNameWithoutExtension(profileFileName);
+
+ // Get all backup files for this specific profile (any extension) sorted by timestamp (newest first)
+ var backupFiles = Directory.GetFiles(backupFolderPath)
+ .Where(f =>
+ {
+ var fileName = Path.GetFileName(f);
+
+ // Check if it's a timestamped backup and contains the profile name
+ return TimestampHelper.IsTimestampedFilename(fileName) &&
+ fileName.Contains($"_{profileNameWithoutExtension}.");
+ })
+ .OrderByDescending(f => TimestampHelper.ExtractTimestampFromFilename(Path.GetFileName(f)))
+ .ToList();
+
+ if (backupFiles.Count > maxBackupFiles)
+ Log.Info($"Cleaning up old backup files for {profileNameWithoutExtension}... Found {backupFiles.Count} backups, keeping the most recent {maxBackupFiles}.");
+
+ // Delete oldest backups until the maximum number is reached
+ while (backupFiles.Count > maxBackupFiles)
+ {
+ var fileToDelete = backupFiles.Last();
+
+ File.Delete(fileToDelete);
+
+ backupFiles.RemoveAt(backupFiles.Count - 1);
+
+ Log.Info($"Backup deleted: {fileToDelete}");
+ }
+ }
+
///
/// Creates a backup of the specified profile file in the given backup folder with the provided backup file name.
///
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index 97834d7515..2d96124243 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -51,7 +51,7 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
///
/// Determines if the welcome dialog should be shown on application start.
- ///
+ /// S
public bool WelcomeDialog_Show
{
get => _welcomeDialog_Show;
diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs
index a0c5c9c1ca..34ff00a850 100644
--- a/Source/NETworkManager.Settings/SettingsManager.cs
+++ b/Source/NETworkManager.Settings/SettingsManager.cs
@@ -228,7 +228,7 @@ private static SettingsInfo DeserializeFromXmlFile(string filePath)
/// Method to save the currently loaded settings (to a file).
///
public static void Save()
- {
+ {
// Create the directory if it does not exist
Directory.CreateDirectory(GetSettingsFolderLocation());
@@ -265,24 +265,27 @@ private static void SerializeToFile(string filePath)
/// called as part of a daily maintenance routine.
private static void CreateDailyBackupIfNeeded()
{
- // Check if backups are disabled
+ // Skip if daily backups are disabled
if (!Current.Settings_IsDailyBackupEnabled)
{
- Log.Debug("Daily backups are disabled. Skipping backup creation...");
+ Log.Info("Daily backups are disabled. Skipping backup creation...");
+ return;
+ }
+ // Skip if settings file doesn't exist yet
+ if (!File.Exists(GetSettingsFilePath()))
+ {
+ Log.Warn("Settings file does not exist yet. Skipping backup creation...");
return;
}
+ // Create backup if needed
var currentDate = DateTime.Now.Date;
-
- if (Current.LastBackup < currentDate)
+ var lastBackupDate = Current.LastBackup.Date;
+
+ if (lastBackupDate < currentDate)
{
- // Check if settings file exists
- if (!File.Exists(GetSettingsFilePath()))
- {
- Log.Warn("Settings file does not exist yet. Skipping backup creation...");
- return;
- }
+ Log.Info("Creating daily backup of settings...");
// Create backup
Backup(GetSettingsFilePath(),
@@ -310,14 +313,25 @@ private static void CreateDailyBackupIfNeeded()
/// The maximum number of backup files to retain. Must be greater than zero.
private static void CleanupBackups(string backupFolderPath, string settingsFileName, int maxBackupFiles)
{
- // Get all backup files sorted by timestamp (newest first)
+ // Extract settings name without extension to match all backup files regardless of extension
+ // (e.g., "Settings" matches "2025-01-19_Settings.json", "2025-01-19_Settings.xml")
+ var settingsNameWithoutExtension = Path.GetFileNameWithoutExtension(settingsFileName);
+
+ // Get all backup files for settings (any extension) sorted by timestamp (newest first)
var backupFiles = Directory.GetFiles(backupFolderPath)
- .Where(f => (f.EndsWith(settingsFileName) || f.EndsWith(GetLegacySettingsFileName())) && TimestampHelper.IsTimestampedFilename(Path.GetFileName(f)))
+ .Where(f =>
+ {
+ var fileName = Path.GetFileName(f);
+
+ // Check if it's a timestamped backup and contains the settings name
+ return TimestampHelper.IsTimestampedFilename(fileName) &&
+ fileName.Contains($"_{settingsNameWithoutExtension}.");
+ })
.OrderByDescending(f => TimestampHelper.ExtractTimestampFromFilename(Path.GetFileName(f)))
.ToList();
if (backupFiles.Count > maxBackupFiles)
- Log.Info($"Cleaning up old backup files... Found {backupFiles.Count} backups, keeping the most recent {maxBackupFiles}.");
+ Log.Info($"Cleaning up old backup files for {settingsNameWithoutExtension}... Found {backupFiles.Count} backups, keeping the most recent {maxBackupFiles}.");
// Delete oldest backups until the maximum number is reached
while (backupFiles.Count > maxBackupFiles)
@@ -365,6 +379,11 @@ public static void Upgrade(Version fromVersion, Version toVersion)
{
Log.Info($"Start settings upgrade from {fromVersion} to {toVersion}...");
+ // Create backup
+ Backup(GetSettingsFilePath(),
+ GetSettingsBackupFolderLocation(),
+ TimestampHelper.GetTimestampFilename(GetSettingsFileName()));
+
// 2023.3.7.0
if (fromVersion < new Version(2023, 3, 7, 0))
UpgradeTo_2023_3_7_0();
diff --git a/Source/NETworkManager.Utilities/TimestampHelper.cs b/Source/NETworkManager.Utilities/TimestampHelper.cs
index 0ed4b5b83b..6ee5e83c09 100644
--- a/Source/NETworkManager.Utilities/TimestampHelper.cs
+++ b/Source/NETworkManager.Utilities/TimestampHelper.cs
@@ -35,7 +35,7 @@ public static bool IsTimestampedFilename(string fileName)
if (fileName.Length < 16)
return false;
- var timestampString = fileName.Substring(0, 14);
+ var timestampString = fileName[..14];
return DateTime.TryParseExact(timestampString, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out _);
}
diff --git a/Source/NETworkManager/App.xaml.cs b/Source/NETworkManager/App.xaml.cs
index b3e4cd3f1f..ede9bc5e36 100644
--- a/Source/NETworkManager/App.xaml.cs
+++ b/Source/NETworkManager/App.xaml.cs
@@ -271,16 +271,25 @@ private void Application_Exit(object sender, ExitEventArgs e)
private void Save()
{
+ // Save settings if they have changed
if (SettingsManager.Current.SettingsChanged)
{
Log.Info("Save application settings...");
SettingsManager.Save();
}
- if (ProfileManager.ProfilesChanged)
+ // Save profiles if they have changed
+ if (ProfileManager.LoadedProfileFile != null && ProfileManager.LoadedProfileFileData != null)
{
- Log.Info("Save current profiles...");
- ProfileManager.Save();
+ if (ProfileManager.LoadedProfileFileData.ProfilesChanged)
+ {
+ Log.Info($"Save current profile file \"{ProfileManager.LoadedProfileFile.Name}\"...");
+ ProfileManager.Save();
+ }
+ }
+ else
+ {
+ Log.Warn("Cannot save profiles because no profile file is loaded or the profile file is encrypted and not yet unlocked.");
}
}
}
From 1598368a8e249441c205a3a8194286725e37ea36 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 1 Feb 2026 01:51:18 +0100
Subject: [PATCH 05/10] Feature: Refactor ProfileManager to add properties to a
ProfileFile
---
.../ProfileFileData.cs | 97 +++-
.../ProfileInfoSerializable.cs | 9 +
.../NETworkManager.Profiles/ProfileManager.cs | 519 +++++++++---------
.../ViewModels/ARPTableAddEntryViewModel.cs | 2 +-
.../ViewModels/ARPTableViewModel.cs | 2 +-
.../ViewModels/AboutViewModel.cs | 2 +-
.../BitCalculatorSettingsViewModel.cs | 2 +-
.../ViewModels/BitCalculatorViewModel.cs | 2 +-
.../ViewModels/CommandLineViewModel.cs | 2 +-
.../ViewModels/ConnectionsViewModel.cs | 2 +-
.../CredentialsChangePasswordViewModel.cs | 2 +-
...CredentialsPasswordProfileFileViewModel.cs | 2 +-
.../CredentialsPasswordViewModel.cs | 2 +-
.../CredentialsSetPasswordViewModel.cs | 2 +-
.../ViewModels/CustomCommandViewModel.cs | 2 +-
.../ViewModels/DNSLookupHostViewModel.cs | 6 +-
.../ViewModels/DNSLookupSettingsViewModel.cs | 2 +-
.../ViewModels/DNSLookupViewModel.cs | 2 +-
.../ViewModels/DashboardSettingsViewModel.cs | 2 +-
.../ViewModels/DashboardViewModel.cs | 2 +-
.../ViewModels/DiscoveryProtocolViewModel.cs | 2 +-
.../ViewModels/DropdownViewModel.cs | 2 +-
.../ViewModels/ExportViewModel.cs | 2 +-
.../ViewModels/GroupViewModel.cs | 2 +-
.../HostsFileEditorEntryViewModel.cs | 2 +-
.../ViewModels/HostsFileEditorViewModel.cs | 2 +-
.../IPAddressAndSubnetmaskViewModel.cs | 2 +-
.../ViewModels/IPAddressViewModel.cs | 2 +-
.../IPApiDNSResolverWidgetViewModel.cs | 2 +-
.../IPApiIPGeolocationWidgetViewModel.cs | 2 +-
.../ViewModels/IPGeolocationHostViewModel.cs | 6 +-
.../ViewModels/IPGeolocationViewModel.cs | 2 +-
.../ViewModels/IPScannerHostViewModel.cs | 6 +-
.../ViewModels/IPScannerSettingsViewModel.cs | 2 +-
.../ViewModels/IPScannerViewModel.cs | 2 +-
.../ViewModels/ListenersViewModel.cs | 2 +-
.../ViewModels/LookupHostViewModel.cs | 2 +-
.../ViewModels/LookupOUILookupViewModel.cs | 2 +-
.../ViewModels/LookupPortViewModel.cs | 2 +-
.../MessageConfirmationViewModel.cs | 2 +-
.../ViewModels/MessageViewModel.cs | 2 +-
.../NetworkConnectionWidgetViewModel.cs | 2 +-
.../ViewModels/NetworkInterfaceViewModel.cs | 6 +-
.../ViewModels/PingMonitorHostViewModel.cs | 6 +-
.../PingMonitorSettingsViewModel.cs | 2 +-
.../ViewModels/PingMonitorViewModel.cs | 2 +-
.../ViewModels/PortProfileViewModel.cs | 2 +-
.../ViewModels/PortProfilesViewModel.cs | 2 +-
.../ViewModels/PortScannerHostViewModel.cs | 6 +-
.../PortScannerSettingsViewModel.cs | 2 +-
.../ViewModels/PortScannerViewModel.cs | 2 +-
.../ViewModels/PowerShellConnectViewModel.cs | 2 +-
.../ViewModels/PowerShellHostViewModel.cs | 6 +-
.../ViewModels/PowerShellSettingsViewModel.cs | 2 +-
.../ViewModels/ProfileFileViewModel.cs | 2 +-
.../ViewModels/ProfileViewModel.cs | 2 +-
.../ViewModels/ProfilesViewModel.cs | 8 +-
.../ViewModels/PuTTYConnectViewModel.cs | 2 +-
.../ViewModels/PuTTYHostViewModel.cs | 6 +-
.../ViewModels/PuTTYSettingsViewModel.cs | 2 +-
.../RemoteDesktopConnectViewModel.cs | 2 +-
.../ViewModels/RemoteDesktopHostViewModel.cs | 6 +-
.../RemoteDesktopSettingsViewModel.cs | 2 +-
.../ViewModels/SNMPHostViewModel.cs | 6 +-
.../ViewModels/SNMPOIDProfileViewModel.cs | 2 +-
.../ViewModels/SNMPOIDProfilesViewModel.cs | 2 +-
.../ViewModels/SNMPSettingsViewModel.cs | 2 +-
.../ViewModels/SNMPViewModel.cs | 2 +-
.../ViewModels/SNTPLookupHostViewModel.cs | 2 +-
.../ViewModels/SNTPLookupSettingsViewModel.cs | 2 +-
.../ViewModels/SNTPLookupViewModel.cs | 2 +-
.../ServerConnectionInfoProfileViewModel.cs | 2 +-
.../ViewModels/SettingsAppearanceViewModel.cs | 2 +-
.../ViewModels/SettingsAutostartViewModel.cs | 2 +-
.../ViewModels/SettingsGeneralViewModel.cs | 2 +-
.../ViewModels/SettingsHotKeysViewModel.cs | 2 +-
.../ViewModels/SettingsLanguageViewModel.cs | 2 +-
.../ViewModels/SettingsNetworkViewModel.cs | 2 +-
.../ViewModels/SettingsProfilesViewModel.cs | 2 +-
.../ViewModels/SettingsSettingsViewModel.cs | 2 +-
.../ViewModels/SettingsStatusViewModel.cs | 2 +-
.../ViewModels/SettingsUpdateViewModel.cs | 2 +-
.../ViewModels/SettingsViewModel.cs | 2 +-
.../ViewModels/SettingsWindowViewModel.cs | 2 +-
.../SubnetCalculatorCalculatorViewModel.cs | 2 +-
.../SubnetCalculatorHostViewModel.cs | 2 +-
.../SubnetCalculatorSubnettingViewModel.cs | 2 +-
.../SubnetCalculatorWideSubnetViewModel.cs | 2 +-
.../ViewModels/TigerVNCConnectViewModel.cs | 2 +-
.../ViewModels/TigerVNCHostViewModel.cs | 6 +-
.../ViewModels/TigerVNCSettingsViewModel.cs | 2 +-
.../ViewModels/TracerouteHostViewModel.cs | 6 +-
.../ViewModels/TracerouteSettingsViewModel.cs | 2 +-
.../ViewModels/TracerouteViewModel.cs | 2 +-
.../ViewModels/UpgradeViewModel.cs | 2 +-
.../ViewModels/ViewModelBase.cs | 2 +-
.../ViewModels/WakeOnLANSettingsViewModel.cs | 2 +-
.../ViewModels/WakeOnLANViewModel.cs | 6 +-
.../ViewModels/WebConsoleConnectViewModel.cs | 2 +-
.../ViewModels/WebConsoleHostViewModel.cs | 6 +-
.../ViewModels/WebConsoleSettingsViewModel.cs | 2 +-
.../ViewModels/WelcomeViewModel.cs | 2 +-
.../ViewModels/WhoisHostViewModel.cs | 6 +-
.../ViewModels/WhoisViewModel.cs | 2 +-
.../ViewModels/WiFiConnectViewModel.cs | 2 +-
.../ViewModels/WiFiViewModel.cs | 2 +-
106 files changed, 513 insertions(+), 384 deletions(-)
diff --git a/Source/NETworkManager.Profiles/ProfileFileData.cs b/Source/NETworkManager.Profiles/ProfileFileData.cs
index db2093e901..fd85c9e62c 100644
--- a/Source/NETworkManager.Profiles/ProfileFileData.cs
+++ b/Source/NETworkManager.Profiles/ProfileFileData.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
@@ -76,7 +77,99 @@ public DateTime? LastBackup
}
///
- /// List of groups containing profiles.
+ /// Working copy of groups containing profiles (runtime objects with SecureString passwords).
+ /// This is the single source of truth for profile data in memory.
+ /// Not serialized directly - use for JSON serialization.
///
- public List Groups { get; set; } = [];
+ [JsonIgnore]
+ public List Groups { get; set; } = [];
+
+ ///
+ /// Serializable representation of groups for JSON persistence.
+ /// Gets: Converts working Groups to serializable format.
+ /// Sets: Converts deserialized data back to working Groups.
+ ///
+ [JsonPropertyName("Groups")]
+ public List GroupsSerializable
+ {
+ get => [.. Groups.Select(g => new GroupInfoSerializable(g)
+ {
+ Profiles = [.. g.Profiles.Where(p => !p.IsDynamic).Select(p => new ProfileInfoSerializable(p)
+ {
+ RemoteDesktop_Password = p.RemoteDesktop_Password != null
+ ? Utilities.SecureStringHelper.ConvertToString(p.RemoteDesktop_Password)
+ : string.Empty,
+ RemoteDesktop_GatewayServerPassword = p.RemoteDesktop_GatewayServerPassword != null
+ ? Utilities.SecureStringHelper.ConvertToString(p.RemoteDesktop_GatewayServerPassword)
+ : string.Empty,
+ SNMP_Community = p.SNMP_Community != null
+ ? Utilities.SecureStringHelper.ConvertToString(p.SNMP_Community)
+ : string.Empty,
+ SNMP_Auth = p.SNMP_Auth != null
+ ? Utilities.SecureStringHelper.ConvertToString(p.SNMP_Auth)
+ : string.Empty,
+ SNMP_Priv = p.SNMP_Priv != null
+ ? Utilities.SecureStringHelper.ConvertToString(p.SNMP_Priv)
+ : string.Empty
+ })],
+ RemoteDesktop_Password = g.RemoteDesktop_Password != null
+ ? Utilities.SecureStringHelper.ConvertToString(g.RemoteDesktop_Password)
+ : string.Empty,
+ RemoteDesktop_GatewayServerPassword = g.RemoteDesktop_GatewayServerPassword != null
+ ? Utilities.SecureStringHelper.ConvertToString(g.RemoteDesktop_GatewayServerPassword)
+ : string.Empty,
+ SNMP_Community = g.SNMP_Community != null
+ ? Utilities.SecureStringHelper.ConvertToString(g.SNMP_Community)
+ : string.Empty,
+ SNMP_Auth = g.SNMP_Auth != null
+ ? Utilities.SecureStringHelper.ConvertToString(g.SNMP_Auth)
+ : string.Empty,
+ SNMP_Priv = g.SNMP_Priv != null
+ ? Utilities.SecureStringHelper.ConvertToString(g.SNMP_Priv)
+ : string.Empty
+ }).Where(g => !g.IsDynamic)];
+
+ set
+ {
+ Groups = value?.Select(gs => new GroupInfo(gs)
+ {
+ Profiles = [.. (gs.Profiles ?? []).Select(ps => new ProfileInfo(ps)
+ {
+ TagsCollection = (ps.TagsCollection == null || ps.TagsCollection.Count == 0) && !string.IsNullOrEmpty(ps.Tags)
+ ? new Controls.ObservableSetCollection(ps.Tags.Split([';'], StringSplitOptions.RemoveEmptyEntries))
+ : ps.TagsCollection,
+ RemoteDesktop_Password = !string.IsNullOrEmpty(ps.RemoteDesktop_Password)
+ ? Utilities.SecureStringHelper.ConvertToSecureString(ps.RemoteDesktop_Password)
+ : null,
+ RemoteDesktop_GatewayServerPassword = !string.IsNullOrEmpty(ps.RemoteDesktop_GatewayServerPassword)
+ ? Utilities.SecureStringHelper.ConvertToSecureString(ps.RemoteDesktop_GatewayServerPassword)
+ : null,
+ SNMP_Community = !string.IsNullOrEmpty(ps.SNMP_Community)
+ ? Utilities.SecureStringHelper.ConvertToSecureString(ps.SNMP_Community)
+ : null,
+ SNMP_Auth = !string.IsNullOrEmpty(ps.SNMP_Auth)
+ ? Utilities.SecureStringHelper.ConvertToSecureString(ps.SNMP_Auth)
+ : null,
+ SNMP_Priv = !string.IsNullOrEmpty(ps.SNMP_Priv)
+ ? Utilities.SecureStringHelper.ConvertToSecureString(ps.SNMP_Priv)
+ : null
+ })],
+ RemoteDesktop_Password = !string.IsNullOrEmpty(gs.RemoteDesktop_Password)
+ ? Utilities.SecureStringHelper.ConvertToSecureString(gs.RemoteDesktop_Password)
+ : null,
+ RemoteDesktop_GatewayServerPassword = !string.IsNullOrEmpty(gs.RemoteDesktop_GatewayServerPassword)
+ ? Utilities.SecureStringHelper.ConvertToSecureString(gs.RemoteDesktop_GatewayServerPassword)
+ : null,
+ SNMP_Community = !string.IsNullOrEmpty(gs.SNMP_Community)
+ ? Utilities.SecureStringHelper.ConvertToSecureString(gs.SNMP_Community)
+ : null,
+ SNMP_Auth = !string.IsNullOrEmpty(gs.SNMP_Auth)
+ ? Utilities.SecureStringHelper.ConvertToSecureString(gs.SNMP_Auth)
+ : null,
+ SNMP_Priv = !string.IsNullOrEmpty(gs.SNMP_Priv)
+ ? Utilities.SecureStringHelper.ConvertToSecureString(gs.SNMP_Priv)
+ : null
+ }).ToList() ?? [];
+ }
+ }
}
diff --git a/Source/NETworkManager.Profiles/ProfileInfoSerializable.cs b/Source/NETworkManager.Profiles/ProfileInfoSerializable.cs
index 16ad3435ab..96e14fbe0f 100644
--- a/Source/NETworkManager.Profiles/ProfileInfoSerializable.cs
+++ b/Source/NETworkManager.Profiles/ProfileInfoSerializable.cs
@@ -2,12 +2,21 @@
public class ProfileInfoSerializable : ProfileInfo
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
public ProfileInfoSerializable()
{
+
}
+ ///
+ /// Initializes a new instance of the class using the specified profile information.
+ ///
+ /// The object that contains the profile data to be serialized. Cannot be null.
public ProfileInfoSerializable(ProfileInfo profile) : base(profile)
{
+
}
///
diff --git a/Source/NETworkManager.Profiles/ProfileManager.cs b/Source/NETworkManager.Profiles/ProfileManager.cs
index e540c02369..9963e88f05 100644
--- a/Source/NETworkManager.Profiles/ProfileManager.cs
+++ b/Source/NETworkManager.Profiles/ProfileManager.cs
@@ -16,7 +16,7 @@ namespace NETworkManager.Profiles;
public static class ProfileManager
{
- #region Variables
+ #region Variables
private static readonly ILog Log = LogManager.GetLogger(typeof(ProfileManager));
///
@@ -113,11 +113,6 @@ private set
}
}
- ///
- /// Currently loaded groups with profiles (working copy in memory).
- ///
- public static List Groups { get; set; } = [];
-
#endregion
#region Constructor
@@ -287,15 +282,21 @@ private static void LoadProfileFiles()
///
/// Method to create a profile file.
///
- ///
+ /// Name of the profile file to create.
+ /// Thrown when profileName is null or empty.
public static void CreateEmptyProfileFile(string profileName)
{
+ ArgumentException.ThrowIfNullOrWhiteSpace(profileName);
+
ProfileFileInfo profileFileInfo = new(profileName,
Path.Combine(GetProfilesFolderLocation(), $"{profileName}{ProfileFileExtension}"));
Directory.CreateDirectory(GetProfilesFolderLocation());
- SerializeToFile(profileFileInfo.Path, []);
+ // Create and serialize empty ProfileFileData to new file (without loading it)
+ var emptyProfileFileData = new ProfileFileData();
+ var jsonString = JsonSerializer.Serialize(emptyProfileFileData, JsonOptions);
+ File.WriteAllText(profileFileInfo.Path, jsonString);
ProfileFiles.Add(profileFileInfo);
}
@@ -305,8 +306,14 @@ public static void CreateEmptyProfileFile(string profileName)
///
/// to rename.
/// New of the profile file.
+ /// Thrown when profileFileInfo is null.
+ /// Thrown when newProfileName is null or empty.
public static void RenameProfileFile(ProfileFileInfo profileFileInfo, string newProfileName)
{
+ ArgumentNullException.ThrowIfNull(profileFileInfo);
+ ArgumentException.ThrowIfNullOrWhiteSpace(newProfileName);
+
+
// Check if the profile is currently in use
var switchProfile = false;
@@ -350,8 +357,12 @@ public static void RenameProfileFile(ProfileFileInfo profileFileInfo, string new
/// Method to delete a profile file.
///
/// to delete.
+ /// Thrown when profileFileInfo is null.
public static void DeleteProfileFile(ProfileFileInfo profileFileInfo)
{
+ ArgumentNullException.ThrowIfNull(profileFileInfo);
+
+
// Trigger switch via UI (to get the password if the file is encrypted), if the selected profile file is deleted
if (LoadedProfileFile != null && LoadedProfileFile.Equals(profileFileInfo))
LoadedProfileFileChanged(ProfileFiles.FirstOrDefault(x => !x.Equals(profileFileInfo)));
@@ -369,8 +380,12 @@ public static void DeleteProfileFile(ProfileFileInfo profileFileInfo)
///
/// which should be encrypted.
/// Password to encrypt the profile file.
+ /// Thrown when profileFileInfo or password is null.
public static void EnableEncryption(ProfileFileInfo profileFileInfo, SecureString password)
{
+ ArgumentNullException.ThrowIfNull(profileFileInfo);
+ ArgumentNullException.ThrowIfNull(password);
+
// Check if the profile is currently in use
var switchProfile = false;
@@ -393,18 +408,32 @@ public static void EnableEncryption(ProfileFileInfo profileFileInfo, SecureStrin
IsPasswordValid = true
};
- List profiles = Path.GetExtension(profileFileInfo.Path) == LegacyProfileFileExtension ?
- DeserializeFromXmlFile(profileFileInfo.Path) :
- DeserializeFromFile(profileFileInfo.Path);
+ // Save current state to prevent corruption
+ var previousLoadedProfileFileData = LoadedProfileFileData;
- // Save the encrypted file
- var decryptedBytes = SerializeToByteArray(profiles);
- var encryptedBytes = CryptoHelper.Encrypt(decryptedBytes,
- SecureStringHelper.ConvertToString(newProfileFileInfo.Password),
- GlobalStaticConfiguration.Profile_EncryptionKeySize,
- GlobalStaticConfiguration.Profile_EncryptionIterations);
+ try
+ {
+ // Load the existing profile data (temporarily overwrites LoadedProfileFileData)
+ if (Path.GetExtension(profileFileInfo.Path) == LegacyProfileFileExtension)
+ DeserializeFromXmlFile(profileFileInfo.Path);
+ else
+ DeserializeFromFile(profileFileInfo.Path);
- File.WriteAllBytes(newProfileFileInfo.Path, encryptedBytes);
+ // Save the encrypted file
+ var decryptedBytes = SerializeToByteArray();
+ var encryptedBytes = CryptoHelper.Encrypt(decryptedBytes,
+ SecureStringHelper.ConvertToString(newProfileFileInfo.Password),
+ GlobalStaticConfiguration.Profile_EncryptionKeySize,
+ GlobalStaticConfiguration.Profile_EncryptionIterations);
+
+ File.WriteAllBytes(newProfileFileInfo.Path, encryptedBytes);
+ }
+ finally
+ {
+ // Restore previous state if this wasn't the currently loaded profile
+ if (!switchProfile)
+ LoadedProfileFileData = previousLoadedProfileFileData;
+ }
// Add the new profile
ProfileFiles.Add(newProfileFileInfo);
@@ -429,9 +458,14 @@ public static void EnableEncryption(ProfileFileInfo profileFileInfo, SecureStrin
/// which should be changed.
/// Password to decrypt the profile file.
/// Password to encrypt the profile file.
+ /// Thrown when profileFileInfo, password, or newPassword is null.
public static void ChangeMasterPassword(ProfileFileInfo profileFileInfo, SecureString password,
SecureString newPassword)
{
+ ArgumentNullException.ThrowIfNull(profileFileInfo);
+ ArgumentNullException.ThrowIfNull(password);
+ ArgumentNullException.ThrowIfNull(newPassword);
+
// Check if the profile is currently in use
var switchProfile = false;
@@ -454,24 +488,37 @@ public static void ChangeMasterPassword(ProfileFileInfo profileFileInfo, SecureS
IsPasswordValid = true
};
- // Load and decrypt the profiles from the profile file
- var encryptedBytes = File.ReadAllBytes(profileFileInfo.Path);
- var decryptedBytes = CryptoHelper.Decrypt(encryptedBytes, SecureStringHelper.ConvertToString(password),
- GlobalStaticConfiguration.Profile_EncryptionKeySize,
- GlobalStaticConfiguration.Profile_EncryptionIterations);
+ // Save current state to prevent corruption
+ var previousLoadedProfileFileData = LoadedProfileFileData;
- List profiles = IsXmlContent(decryptedBytes) ?
- DeserializeFromXmlByteArray(decryptedBytes) :
- DeserializeFromByteArray(decryptedBytes);
+ try
+ {
+ // Load and decrypt the profiles from the profile file (temporarily overwrites LoadedProfileFileData)
+ var encryptedBytes = File.ReadAllBytes(profileFileInfo.Path);
+ var decryptedBytes = CryptoHelper.Decrypt(encryptedBytes, SecureStringHelper.ConvertToString(password),
+ GlobalStaticConfiguration.Profile_EncryptionKeySize,
+ GlobalStaticConfiguration.Profile_EncryptionIterations);
+
+ if (IsXmlContent(decryptedBytes))
+ DeserializeFromXmlByteArray(decryptedBytes);
+ else
+ DeserializeFromByteArray(decryptedBytes);
- // Save the encrypted file
- decryptedBytes = SerializeToByteArray(profiles);
- encryptedBytes = CryptoHelper.Encrypt(decryptedBytes,
- SecureStringHelper.ConvertToString(newProfileFileInfo.Password),
- GlobalStaticConfiguration.Profile_EncryptionKeySize,
- GlobalStaticConfiguration.Profile_EncryptionIterations);
+ // Save the encrypted file with new password
+ decryptedBytes = SerializeToByteArray();
+ encryptedBytes = CryptoHelper.Encrypt(decryptedBytes,
+ SecureStringHelper.ConvertToString(newProfileFileInfo.Password),
+ GlobalStaticConfiguration.Profile_EncryptionKeySize,
+ GlobalStaticConfiguration.Profile_EncryptionIterations);
- File.WriteAllBytes(newProfileFileInfo.Path, encryptedBytes);
+ File.WriteAllBytes(newProfileFileInfo.Path, encryptedBytes);
+ }
+ finally
+ {
+ // Restore previous state if this wasn't the currently loaded profile
+ if (!switchProfile)
+ LoadedProfileFileData = previousLoadedProfileFileData;
+ }
// Add the new profile
ProfileFiles.Add(newProfileFileInfo);
@@ -492,8 +539,12 @@ public static void ChangeMasterPassword(ProfileFileInfo profileFileInfo, SecureS
///
/// which should be decrypted.
/// Password to decrypt the profile file.
+ /// Thrown when profileFileInfo or password is null.
public static void DisableEncryption(ProfileFileInfo profileFileInfo, SecureString password)
{
+ ArgumentNullException.ThrowIfNull(profileFileInfo);
+ ArgumentNullException.ThrowIfNull(password);
+
// Check if the profile is currently in use
var switchProfile = false;
@@ -512,18 +563,31 @@ public static void DisableEncryption(ProfileFileInfo profileFileInfo, SecureStri
var newProfileFileInfo = new ProfileFileInfo(profileFileInfo.Name,
Path.ChangeExtension(profileFileInfo.Path, ProfileFileExtension));
- // Load and decrypt the profiles from the profile file
- var encryptedBytes = File.ReadAllBytes(profileFileInfo.Path);
- var decryptedBytes = CryptoHelper.Decrypt(encryptedBytes, SecureStringHelper.ConvertToString(password),
- GlobalStaticConfiguration.Profile_EncryptionKeySize,
- GlobalStaticConfiguration.Profile_EncryptionIterations);
+ // Save current state to prevent corruption
+ var previousLoadedProfileFileData = LoadedProfileFileData;
- List profiles = IsXmlContent(decryptedBytes) ?
- DeserializeFromXmlByteArray(decryptedBytes) :
- DeserializeFromByteArray(decryptedBytes);
+ try
+ {
+ // Load and decrypt the profiles from the profile file (temporarily overwrites LoadedProfileFileData)
+ var encryptedBytes = File.ReadAllBytes(profileFileInfo.Path);
+ var decryptedBytes = CryptoHelper.Decrypt(encryptedBytes, SecureStringHelper.ConvertToString(password),
+ GlobalStaticConfiguration.Profile_EncryptionKeySize,
+ GlobalStaticConfiguration.Profile_EncryptionIterations);
+
+ if (IsXmlContent(decryptedBytes))
+ DeserializeFromXmlByteArray(decryptedBytes);
+ else
+ DeserializeFromByteArray(decryptedBytes);
- // Save the decrypted profiles to the profile file
- SerializeToFile(newProfileFileInfo.Path, profiles);
+ // Save the decrypted profiles to the profile file
+ SerializeToFile(newProfileFileInfo.Path);
+ }
+ finally
+ {
+ // Restore previous state if this wasn't the currently loaded profile
+ if (!switchProfile)
+ LoadedProfileFileData = previousLoadedProfileFileData;
+ }
// Add the new profile
ProfileFiles.Add(newProfileFileInfo);
@@ -565,8 +629,6 @@ private static void Load(ProfileFileInfo profileFileInfo)
GlobalStaticConfiguration.Profile_EncryptionKeySize,
GlobalStaticConfiguration.Profile_EncryptionIterations);
- List groups;
-
if (IsXmlContent(decryptedBytes))
{
//
@@ -576,7 +638,7 @@ private static void Load(ProfileFileInfo profileFileInfo)
Log.Info($"Legacy XML profile file detected inside encrypted profile: {profileFileInfo.Path}. Migration in progress...");
// Load from legacy XML byte array
- groups = DeserializeFromXmlByteArray(decryptedBytes);
+ DeserializeFromXmlByteArray(decryptedBytes);
// Create a backup of the legacy XML file
Backup(profileFileInfo.Path,
@@ -584,7 +646,7 @@ private static void Load(ProfileFileInfo profileFileInfo)
TimestampHelper.GetTimestampFilename(Path.GetFileName(profileFileInfo.Path)));
// Save encrypted profile file with new JSON format
- var newDecryptedBytes = SerializeToByteArray([.. groups]);
+ var newDecryptedBytes = SerializeToByteArray();
var newEncryptedBytes = CryptoHelper.Encrypt(newDecryptedBytes,
SecureStringHelper.ConvertToString(profileFileInfo.Password),
GlobalStaticConfiguration.Profile_EncryptionKeySize,
@@ -596,11 +658,9 @@ private static void Load(ProfileFileInfo profileFileInfo)
}
else
{
- groups = DeserializeFromByteArray(decryptedBytes);
+ DeserializeFromByteArray(decryptedBytes);
}
- AddGroups(groups, false);
-
// Password is valid
ProfileFiles.FirstOrDefault(x => x.Equals(profileFileInfo))!.IsPasswordValid = true;
profileFileInfo.IsPasswordValid = true;
@@ -609,8 +669,6 @@ private static void Load(ProfileFileInfo profileFileInfo)
// Unencrypted profile file
else
{
- List groups;
-
if (Path.GetExtension(profileFileInfo.Path) == LegacyProfileFileExtension)
{
//
@@ -619,7 +677,7 @@ private static void Load(ProfileFileInfo profileFileInfo)
Log.Info($"Legacy XML profile file detected: {profileFileInfo.Path}. Migration in progress...");
// Load from legacy XML file
- groups = DeserializeFromXmlFile(profileFileInfo.Path);
+ DeserializeFromXmlFile(profileFileInfo.Path);
LoadedProfileFile = profileFileInfo;
@@ -633,7 +691,7 @@ private static void Load(ProfileFileInfo profileFileInfo)
Path.ChangeExtension(profileFileInfo.Path, ProfileFileExtension));
// Save new JSON file
- SerializeToFile(newProfileFileInfo.Path, groups);
+ SerializeToFile(newProfileFileInfo.Path);
// Notify migration started
ProfileMigrationStarted();
@@ -658,10 +716,8 @@ private static void Load(ProfileFileInfo profileFileInfo)
}
else
{
- groups = DeserializeFromFile(profileFileInfo.Path);
+ DeserializeFromFile(profileFileInfo.Path);
}
-
- AddGroups(groups, false);
}
}
else
@@ -675,6 +731,9 @@ private static void Load(ProfileFileInfo profileFileInfo)
if (loadedProfileUpdated)
LoadedProfileFileChanged(LoadedProfileFile, true);
+
+ // Notify subscribers that profiles have been loaded/updated
+ ProfilesUpdated(false);
}
///
@@ -701,7 +760,7 @@ public static void Save()
// Only if the password provided earlier was valid...
if (LoadedProfileFile.IsPasswordValid)
{
- var decryptedBytes = SerializeToByteArray([.. Groups]);
+ var decryptedBytes = SerializeToByteArray();
var encryptedBytes = CryptoHelper.Encrypt(decryptedBytes,
SecureStringHelper.ConvertToString(LoadedProfileFile.Password),
GlobalStaticConfiguration.Profile_EncryptionKeySize,
@@ -712,7 +771,7 @@ public static void Save()
}
else
{
- SerializeToFile(LoadedProfileFile.Path, [.. Groups]);
+ SerializeToFile(LoadedProfileFile.Path);
}
LoadedProfileFileData?.ProfilesChanged = false;
@@ -728,11 +787,10 @@ public static void Unload(bool saveLoadedProfiles = true)
Save();
LoadedProfileFile = null;
- LoadedProfileFileData = null;
+ LoadedProfileFileData = new ProfileFileData();
- Groups.Clear();
-
- ProfilesUpdated();
+ // Don't mark as changed since we just unloaded
+ ProfilesUpdated(false);
}
///
@@ -752,165 +810,93 @@ public static void Switch(ProfileFileInfo info, bool saveLoadedProfiles = true)
#region Serialize and deserialize
///
- /// Method to serialize a list of groups as to a JSON file.
+ /// Method to serialize profile data to a JSON file.
///
/// Path to a JSON file.
- /// List of the groups as to serialize.
- private static void SerializeToFile(string filePath, List groups)
+ private static void SerializeToFile(string filePath)
{
// Ensure LoadedProfileFileData exists
LoadedProfileFileData ??= new ProfileFileData();
- // Update LoadedProfileFileData with current groups
- LoadedProfileFileData.Groups = SerializeGroup(groups);
-
var jsonString = JsonSerializer.Serialize(LoadedProfileFileData, JsonOptions);
File.WriteAllText(filePath, jsonString);
}
///
- /// Method to serialize a list of groups as to a byte array.
+ /// Method to serialize profile data to a byte array.
///
- /// List of the groups as to serialize.
- /// Serialized list of groups as as byte array.
- private static byte[] SerializeToByteArray(List groups)
+ /// Serialized profile data as byte array.
+ private static byte[] SerializeToByteArray()
{
// Ensure LoadedProfileFileData exists
LoadedProfileFileData ??= new ProfileFileData();
- // Update LoadedProfileFileData with current groups
- LoadedProfileFileData.Groups = SerializeGroup(groups);
-
var jsonString = JsonSerializer.Serialize(LoadedProfileFileData, JsonOptions);
return Encoding.UTF8.GetBytes(jsonString);
}
///
- /// Method to serialize a list of groups as .
- ///
- /// List of the groups as to serialize.
- /// Serialized list of groups as .
- private static List SerializeGroup(List groups)
- {
- List groupsSerializable = [];
-
- foreach (var group in groups)
- {
- // Don't save temp groups
- if (group.IsDynamic)
- continue;
-
- var profilesSerializable = (from profile in @group.Profiles
- where !profile.IsDynamic
- select new ProfileInfoSerializable(profile)
- {
- RemoteDesktop_Password = profile.RemoteDesktop_Password != null
- ? SecureStringHelper.ConvertToString(profile.RemoteDesktop_Password)
- : string.Empty,
- RemoteDesktop_GatewayServerPassword = profile.RemoteDesktop_GatewayServerPassword != null
- ? SecureStringHelper.ConvertToString(profile.RemoteDesktop_GatewayServerPassword)
- : string.Empty,
- SNMP_Community = profile.SNMP_Community != null
- ? SecureStringHelper.ConvertToString(profile.SNMP_Community)
- : string.Empty,
- SNMP_Auth = profile.SNMP_Auth != null
- ? SecureStringHelper.ConvertToString(profile.SNMP_Auth)
- : string.Empty,
- SNMP_Priv = profile.SNMP_Priv != null
- ? SecureStringHelper.ConvertToString(profile.SNMP_Priv)
- : string.Empty
- }).ToList();
-
- groupsSerializable.Add(new GroupInfoSerializable(group)
- {
- Profiles = profilesSerializable,
- RemoteDesktop_Password = group.RemoteDesktop_Password != null
- ? SecureStringHelper.ConvertToString(group.RemoteDesktop_Password)
- : string.Empty,
- RemoteDesktop_GatewayServerPassword = group.RemoteDesktop_GatewayServerPassword != null
- ? SecureStringHelper.ConvertToString(group.RemoteDesktop_GatewayServerPassword)
- : string.Empty,
- SNMP_Community = group.SNMP_Community != null
- ? SecureStringHelper.ConvertToString(group.SNMP_Community)
- : string.Empty,
- SNMP_Auth =
- group.SNMP_Auth != null ? SecureStringHelper.ConvertToString(group.SNMP_Auth) : string.Empty,
- SNMP_Priv =
- group.SNMP_Priv != null ? SecureStringHelper.ConvertToString(group.SNMP_Priv) : string.Empty
- });
- }
-
- return groupsSerializable;
- }
-
- ///
- /// Method to deserialize a list of groups as from a JSON file.
+ /// Method to deserialize profile data from a JSON file.
///
/// Path to a JSON file.
- /// List of groups as .
- private static List DeserializeFromFile(string filePath)
+ private static void DeserializeFromFile(string filePath)
{
var jsonString = File.ReadAllText(filePath);
- return DeserializeFromJson(jsonString);
+ DeserializeFromJson(jsonString);
}
///
/// Method to deserialize a list of groups as from a legacy XML file.
///
/// Path to an XML file.
- /// List of groups as .
[Obsolete("Legacy XML profile files are no longer used, but the method is kept for migration purposes.")]
- private static List DeserializeFromXmlFile(string filePath)
+ private static void DeserializeFromXmlFile(string filePath)
{
using FileStream fileStream = new(filePath, FileMode.Open);
- return DeserializeFromXmlStream(fileStream);
+ DeserializeFromXmlStream(fileStream);
}
///
- /// Method to deserialize a list of groups as from a byte array.
+ /// Method to deserialize profile data from a byte array.
///
- /// Serialized list of groups as as byte array.
- /// List of groups as .
- private static List DeserializeFromByteArray(byte[] data)
+ /// Serialized profile data as byte array.
+ private static void DeserializeFromByteArray(byte[] data)
{
var jsonString = Encoding.UTF8.GetString(data);
- return DeserializeFromJson(jsonString);
+ DeserializeFromJson(jsonString);
}
///
/// Method to deserialize a list of groups as from a legacy XML byte array.
///
/// Serialized list of groups as as XML byte array.
- /// List of groups as .
[Obsolete("Legacy XML profile files are no longer used, but the method is kept for migration purposes.")]
- private static List DeserializeFromXmlByteArray(byte[] xml)
+ private static void DeserializeFromXmlByteArray(byte[] xml)
{
using MemoryStream memoryStream = new(xml);
- return DeserializeFromXmlStream(memoryStream);
+ DeserializeFromXmlStream(memoryStream);
}
///
- /// Method to deserialize a list of groups as from JSON string.
+ /// Method to deserialize profile data from JSON string.
///
/// JSON string to deserialize.
- /// List of groups as .
- private static List DeserializeFromJson(string jsonString)
+ private static void DeserializeFromJson(string jsonString)
{
try
{
var profileFileData = JsonSerializer.Deserialize(jsonString, JsonOptions);
- if (profileFileData?.Groups != null)
+ if (profileFileData != null)
{
LoadedProfileFileData = profileFileData;
-
- return DeserializeGroup(profileFileData.Groups);
+ return;
}
}
catch (JsonException)
@@ -927,21 +913,18 @@ private static List DeserializeFromJson(string jsonString)
// Create ProfileFileData wrapper for legacy format
LoadedProfileFileData = new ProfileFileData
{
- Groups = groupsSerializable
+ GroupsSerializable = groupsSerializable
};
Log.Info("Successfully loaded profile file in legacy format. It will be migrated to new format on next save.");
-
- return DeserializeGroup(groupsSerializable);
}
///
/// Method to deserialize a list of groups as from an XML stream.
///
/// Stream to deserialize.
- /// List of groups as .
[Obsolete("Legacy XML profile files are no longer used, but the method is kept for migration purposes.")]
- private static List DeserializeFromXmlStream(Stream stream)
+ private static void DeserializeFromXmlStream(Stream stream)
{
XmlSerializer xmlSerializer = new(typeof(List));
@@ -950,7 +933,10 @@ private static List DeserializeFromXmlStream(Stream stream)
if (groupsSerializable == null)
throw new InvalidOperationException("Failed to deserialize XML profile file.");
- return DeserializeGroup(groupsSerializable);
+ LoadedProfileFileData = new ProfileFileData
+ {
+ GroupsSerializable = groupsSerializable
+ };
}
///
@@ -978,95 +964,47 @@ private static bool IsXmlContent(byte[] data)
}
}
- ///
- /// Method to deserialize a list of groups as .
- ///
- /// List of serializable groups to deserialize.
- /// List of groups as .
- private static List DeserializeGroup(List groupsSerializable)
- {
- if (groupsSerializable == null)
- throw new ArgumentNullException(nameof(groupsSerializable));
-
- return [.. from groupSerializable in groupsSerializable
- let profiles = (groupSerializable.Profiles ?? new List()).Select(profileSerializable => new ProfileInfo(profileSerializable)
- {
- // Migrate old tags to new tags list
- // if TagsList is null or empty and Tags is not null or empty, split Tags by ';' and create a new ObservableSetCollection
- // else use the existing TagsList
- TagsCollection = (profileSerializable.TagsCollection == null || profileSerializable.TagsCollection.Count == 0) && !string.IsNullOrEmpty(profileSerializable.Tags) ?
- new Controls.ObservableSetCollection(profileSerializable.Tags.Split([';'], StringSplitOptions.RemoveEmptyEntries)) :
- profileSerializable.TagsCollection,
-
- // Convert passwords to secure strings
- RemoteDesktop_Password = !string.IsNullOrEmpty(profileSerializable.RemoteDesktop_Password)
- ? SecureStringHelper.ConvertToSecureString(profileSerializable.RemoteDesktop_Password)
- : null,
- RemoteDesktop_GatewayServerPassword =
- !string.IsNullOrEmpty(profileSerializable.RemoteDesktop_GatewayServerPassword)
- ? SecureStringHelper.ConvertToSecureString(profileSerializable
- .RemoteDesktop_GatewayServerPassword)
- : null,
- SNMP_Community = !string.IsNullOrEmpty(profileSerializable.SNMP_Community)
- ? SecureStringHelper.ConvertToSecureString(profileSerializable.SNMP_Community)
- : null,
- SNMP_Auth = !string.IsNullOrEmpty(profileSerializable.SNMP_Auth)
- ? SecureStringHelper.ConvertToSecureString(profileSerializable.SNMP_Auth)
- : null,
- SNMP_Priv = !string.IsNullOrEmpty(profileSerializable.SNMP_Priv)
- ? SecureStringHelper.ConvertToSecureString(profileSerializable.SNMP_Priv)
- : null
- })
- .ToList()
- select new GroupInfo(groupSerializable)
- {
- Profiles = profiles,
-
- // Convert passwords to secure strings
- RemoteDesktop_Password = !string.IsNullOrEmpty(groupSerializable.RemoteDesktop_Password)
- ? SecureStringHelper.ConvertToSecureString(groupSerializable.RemoteDesktop_Password)
- : null,
- RemoteDesktop_GatewayServerPassword =
- !string.IsNullOrEmpty(groupSerializable.RemoteDesktop_GatewayServerPassword)
- ? SecureStringHelper.ConvertToSecureString(
- groupSerializable.RemoteDesktop_GatewayServerPassword)
- : null,
- SNMP_Community = !string.IsNullOrEmpty(groupSerializable.SNMP_Community)
- ? SecureStringHelper.ConvertToSecureString(groupSerializable.SNMP_Community)
- : null,
- SNMP_Auth = !string.IsNullOrEmpty(groupSerializable.SNMP_Auth)
- ? SecureStringHelper.ConvertToSecureString(groupSerializable.SNMP_Auth)
- : null,
- SNMP_Priv = !string.IsNullOrEmpty(groupSerializable.SNMP_Priv)
- ? SecureStringHelper.ConvertToSecureString(groupSerializable.SNMP_Priv)
- : null
- }];
- }
-
#endregion
#region Add, remove, replace group(s) and more.
///
- /// Method to add a list of to the list.
+ /// Method to add a list of to the loaded profile data.
///
/// List of groups as to add.
+ /// Thrown when groups collection is null.
private static void AddGroups(List groups, bool profilesChanged = true)
{
+ ArgumentNullException.ThrowIfNull(groups);
+
+ var skippedCount = 0;
foreach (var group in groups)
- Groups.Add(group);
+ {
+ if (group is null)
+ {
+ skippedCount++;
+ continue;
+ }
+
+ LoadedProfileFileData.Groups.Add(group);
+ }
+
+ if (skippedCount > 0)
+ Log.Warn($"AddGroups skipped {skippedCount} null group(s) in collection.");
ProfilesUpdated(profilesChanged);
}
-
+
///
- /// Method to add a to the list.
+ /// Method to add a to the loaded profile data.
///
/// Group as to add.
+ /// Thrown when group is null.
public static void AddGroup(GroupInfo group, bool profilesChanged = true)
-
{
- Groups.Add(group);
+ ArgumentNullException.ThrowIfNull(group);
+
+ LoadedProfileFileData.Groups.Add(group);
ProfilesUpdated(profilesChanged);
}
@@ -1076,9 +1014,19 @@ public static void AddGroup(GroupInfo group, bool profilesChanged = true)
///
/// Name of the group.
/// Group as .
+ /// Thrown when name is null or empty.
+ /// Thrown when group with specified name is not found.
public static GroupInfo GetGroupByName(string name)
{
- return Groups.First(x => x.Name.Equals(name));
+ ArgumentException.ThrowIfNullOrWhiteSpace(name);
+
+
+ var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(name));
+
+ if (group == null)
+ throw new InvalidOperationException($"Group '{name}' not found.");
+
+ return group;
}
///
@@ -1086,10 +1034,14 @@ public static GroupInfo GetGroupByName(string name)
///
/// Old group as .
/// New group as .
+ /// Thrown when oldGroup or newGroup is null.
public static void ReplaceGroup(GroupInfo oldGroup, GroupInfo newGroup)
{
- Groups.Remove(oldGroup);
- Groups.Add(newGroup);
+ ArgumentNullException.ThrowIfNull(oldGroup);
+ ArgumentNullException.ThrowIfNull(newGroup);
+
+ LoadedProfileFileData.Groups.Remove(oldGroup);
+ LoadedProfileFileData.Groups.Add(newGroup);
ProfilesUpdated();
}
@@ -1098,9 +1050,12 @@ public static void ReplaceGroup(GroupInfo oldGroup, GroupInfo newGroup)
/// Method to remove a group.
///
/// Group as to remove
+ /// Thrown when group is null.
public static void RemoveGroup(GroupInfo group)
{
- Groups.Remove(group);
+ ArgumentNullException.ThrowIfNull(group);
+
+ LoadedProfileFileData.Groups.Remove(group);
ProfilesUpdated();
}
@@ -1111,7 +1066,7 @@ public static void RemoveGroup(GroupInfo group)
/// List of group names.
public static IReadOnlyCollection GetGroupNames()
{
- return (from groups in Groups where !groups.IsDynamic select groups.Name).ToList();
+ return (from groups in LoadedProfileFileData.Groups where !groups.IsDynamic select groups.Name).ToList();
}
///
@@ -1121,7 +1076,7 @@ public static IReadOnlyCollection GetGroupNames()
/// True if the profile exists.
public static bool GroupExists(string name)
{
- return Groups.Any(group => group.Name == name);
+ return LoadedProfileFileData.Groups.Any(group => group.Name == name);
}
///
@@ -1131,7 +1086,8 @@ public static bool GroupExists(string name)
/// True if the group has no profiles.
public static bool IsGroupEmpty(string name)
{
- return Groups.FirstOrDefault(x => x.Name == name)!.Profiles.Count == 0;
+ var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name == name);
+ return group?.Profiles.Count == 0;
}
#endregion
@@ -1142,12 +1098,23 @@ public static bool IsGroupEmpty(string name)
/// Method to add a profile to a group.
///
/// Profile as to add.
+ /// Thrown when profile is null.
+ /// Thrown when profile.Group is null or empty.
+ /// Thrown when profile's group is not found after creation attempt.
public static void AddProfile(ProfileInfo profile)
{
+ ArgumentNullException.ThrowIfNull(profile);
+ ArgumentException.ThrowIfNullOrWhiteSpace(profile.Group, nameof(profile));
+
if (!GroupExists(profile.Group))
AddGroup(new GroupInfo(profile.Group));
- Groups.First(x => x.Name.Equals(profile.Group)).Profiles.Add(profile);
+ var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(profile.Group));
+
+ if (group == null)
+ throw new InvalidOperationException($"Group '{profile.Group}' not found for profile after creation attempt.");
+
+ group.Profiles.Add(profile);
ProfilesUpdated();
}
@@ -1157,16 +1124,34 @@ public static void AddProfile(ProfileInfo profile)
///
/// Old profile as .
/// New profile as .
+ /// Thrown when oldProfile or newProfile is null.
+ /// Thrown when profile groups are null or empty.
+ /// Thrown when old profile's group is not found.
public static void ReplaceProfile(ProfileInfo oldProfile, ProfileInfo newProfile)
{
- // Remove
- Groups.First(x => x.Name.Equals(oldProfile.Group)).Profiles.Remove(oldProfile);
+ ArgumentNullException.ThrowIfNull(oldProfile);
+ ArgumentNullException.ThrowIfNull(newProfile);
+ ArgumentException.ThrowIfNullOrWhiteSpace(oldProfile.Group, nameof(oldProfile));
+ ArgumentException.ThrowIfNullOrWhiteSpace(newProfile.Group, nameof(newProfile));
+
+ // Remove from old group
+ var oldGroup = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(oldProfile.Group));
+
+ if (oldGroup == null)
+ throw new InvalidOperationException($"Group '{oldProfile.Group}' not found for old profile.");
+
+ oldGroup.Profiles.Remove(oldProfile);
- // Add
+ // Add to new group (create if doesn't exist)
if (!GroupExists(newProfile.Group))
AddGroup(new GroupInfo(newProfile.Group));
- Groups.First(x => x.Name.Equals(newProfile.Group)).Profiles.Add(newProfile);
+ var newGroup = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(newProfile.Group));
+
+ if (newGroup == null)
+ throw new InvalidOperationException($"Group '{newProfile.Group}' not found for new profile after creation attempt.");
+
+ newGroup.Profiles.Add(newProfile);
ProfilesUpdated();
}
@@ -1175,9 +1160,20 @@ public static void ReplaceProfile(ProfileInfo oldProfile, ProfileInfo newProfile
/// Method to remove a profile from a group.
///
/// Profile as to remove.
+ /// Thrown when profile is null.
+ /// Thrown when profile.Group is null or empty.
+ /// Thrown when profile's group is not found.
public static void RemoveProfile(ProfileInfo profile)
{
- Groups.First(x => x.Name.Equals(profile.Group)).Profiles.Remove(profile);
+ ArgumentNullException.ThrowIfNull(profile);
+ ArgumentException.ThrowIfNullOrWhiteSpace(profile.Group, nameof(profile));
+
+ var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(profile.Group));
+
+ if (group == null)
+ throw new InvalidOperationException($"Group '{profile.Group}' not found.");
+
+ group.Profiles.Remove(profile);
ProfilesUpdated();
}
@@ -1186,10 +1182,34 @@ public static void RemoveProfile(ProfileInfo profile)
/// Method to remove a list of profiles from a group.
///
/// List of profiles as to remove.
+ /// Thrown when profiles collection is null.
public static void RemoveProfiles(IEnumerable profiles)
{
+ ArgumentNullException.ThrowIfNull(profiles);
+
+ var skippedCount = 0;
foreach (var profile in profiles)
- Groups.First(x => x.Name.Equals(profile.Group)).Profiles.Remove(profile);
+ {
+ if (profile is null || string.IsNullOrWhiteSpace(profile.Group))
+ {
+ skippedCount++;
+ continue;
+ }
+
+ var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(profile.Group));
+
+ if (group == null)
+ {
+ Log.Warn($"RemoveProfiles: Group '{profile.Group}' not found for profile '{profile.Name ?? ""}'.");
+ skippedCount++;
+ continue;
+ }
+
+ group.Profiles.Remove(profile);
+ }
+
+ if (skippedCount > 0)
+ Log.Warn($"RemoveProfiles skipped {skippedCount} null or invalid profile(s) in collection.");
ProfilesUpdated();
}
@@ -1256,6 +1276,13 @@ private static void CreateDailyBackupIfNeeded()
/// The maximum number of backup files to retain. Must be greater than zero.
private static void CleanupBackups(string backupFolderPath, string profileFileName, int maxBackupFiles)
{
+ // Skip if backup directory doesn't exist
+ if (!Directory.Exists(backupFolderPath))
+ {
+ Log.Error($"Backup directory does not exist: {backupFolderPath}. Cannot cleanup old backups.");
+ return;
+ }
+
// Extract profile name without extension to match all backup files regardless of extension
// (e.g., "Default" matches "2025-01-19_Default.json", "2025-01-19_Default.encrypted", etc.)
var profileNameWithoutExtension = Path.GetFileNameWithoutExtension(profileFileName);
diff --git a/Source/NETworkManager/ViewModels/ARPTableAddEntryViewModel.cs b/Source/NETworkManager/ViewModels/ARPTableAddEntryViewModel.cs
index ddf627f729..13e1aa6377 100644
--- a/Source/NETworkManager/ViewModels/ARPTableAddEntryViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ARPTableAddEntryViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Windows.Input;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/ARPTableViewModel.cs b/Source/NETworkManager/ViewModels/ARPTableViewModel.cs
index dcab0e5f5a..a048dfe085 100644
--- a/Source/NETworkManager/ViewModels/ARPTableViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ARPTableViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
diff --git a/Source/NETworkManager/ViewModels/AboutViewModel.cs b/Source/NETworkManager/ViewModels/AboutViewModel.cs
index 2bd8c56f92..5952bb1fd7 100644
--- a/Source/NETworkManager/ViewModels/AboutViewModel.cs
+++ b/Source/NETworkManager/ViewModels/AboutViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Documentation;
+using NETworkManager.Documentation;
using NETworkManager.Localization.Resources;
using NETworkManager.Properties;
using NETworkManager.Settings;
diff --git a/Source/NETworkManager/ViewModels/BitCalculatorSettingsViewModel.cs b/Source/NETworkManager/ViewModels/BitCalculatorSettingsViewModel.cs
index 99e89b2bb1..503e53192f 100644
--- a/Source/NETworkManager/ViewModels/BitCalculatorSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/BitCalculatorSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using NETworkManager.Models.Network;
diff --git a/Source/NETworkManager/ViewModels/BitCalculatorViewModel.cs b/Source/NETworkManager/ViewModels/BitCalculatorViewModel.cs
index 5ff6cc9aca..80d9865986 100644
--- a/Source/NETworkManager/ViewModels/BitCalculatorViewModel.cs
+++ b/Source/NETworkManager/ViewModels/BitCalculatorViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
diff --git a/Source/NETworkManager/ViewModels/CommandLineViewModel.cs b/Source/NETworkManager/ViewModels/CommandLineViewModel.cs
index 08ba26c985..a840e295b4 100644
--- a/Source/NETworkManager/ViewModels/CommandLineViewModel.cs
+++ b/Source/NETworkManager/ViewModels/CommandLineViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using System.Windows.Input;
using NETworkManager.Documentation;
diff --git a/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs b/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs
index 996707a1f8..f58de949d6 100644
--- a/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization;
diff --git a/Source/NETworkManager/ViewModels/CredentialsChangePasswordViewModel.cs b/Source/NETworkManager/ViewModels/CredentialsChangePasswordViewModel.cs
index 9d13ed0955..2025a2c7d3 100644
--- a/Source/NETworkManager/ViewModels/CredentialsChangePasswordViewModel.cs
+++ b/Source/NETworkManager/ViewModels/CredentialsChangePasswordViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Security;
using System.Windows.Input;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/CredentialsPasswordProfileFileViewModel.cs b/Source/NETworkManager/ViewModels/CredentialsPasswordProfileFileViewModel.cs
index e7b0e47069..967221d352 100644
--- a/Source/NETworkManager/ViewModels/CredentialsPasswordProfileFileViewModel.cs
+++ b/Source/NETworkManager/ViewModels/CredentialsPasswordProfileFileViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Utilities;
+using NETworkManager.Utilities;
using System;
using System.Security;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/CredentialsPasswordViewModel.cs b/Source/NETworkManager/ViewModels/CredentialsPasswordViewModel.cs
index dbebb8ce62..0a793c49fb 100644
--- a/Source/NETworkManager/ViewModels/CredentialsPasswordViewModel.cs
+++ b/Source/NETworkManager/ViewModels/CredentialsPasswordViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Security;
using System.Windows.Input;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/CredentialsSetPasswordViewModel.cs b/Source/NETworkManager/ViewModels/CredentialsSetPasswordViewModel.cs
index 32c047a8cd..2e34b8ea5d 100644
--- a/Source/NETworkManager/ViewModels/CredentialsSetPasswordViewModel.cs
+++ b/Source/NETworkManager/ViewModels/CredentialsSetPasswordViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Security;
using System.Windows.Input;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/CustomCommandViewModel.cs b/Source/NETworkManager/ViewModels/CustomCommandViewModel.cs
index b10e59b342..aee2951ac5 100644
--- a/Source/NETworkManager/ViewModels/CustomCommandViewModel.cs
+++ b/Source/NETworkManager/ViewModels/CustomCommandViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Windows.Input;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs
index 7db27bdec4..b2b195de47 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Models;
@@ -684,7 +684,7 @@ public void OnViewHide()
///
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.DNSLookup_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.DNSLookup_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -712,7 +712,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.DNSLookup_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.DNSLookup_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.DNSLookup_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs
index 8ab7dcdaf4..12c3dbf8b2 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using DnsClient;
+using DnsClient;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
using NETworkManager.Models.Network;
diff --git a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
index 5e2a38a1fc..047fb86344 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupViewModel.cs
@@ -1,4 +1,4 @@
-using DnsClient;
+using DnsClient;
using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
diff --git a/Source/NETworkManager/ViewModels/DashboardSettingsViewModel.cs b/Source/NETworkManager/ViewModels/DashboardSettingsViewModel.cs
index 87b6abefa4..7b06ed3940 100644
--- a/Source/NETworkManager/ViewModels/DashboardSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DashboardSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Settings;
+using NETworkManager.Settings;
namespace NETworkManager.ViewModels;
diff --git a/Source/NETworkManager/ViewModels/DashboardViewModel.cs b/Source/NETworkManager/ViewModels/DashboardViewModel.cs
index c9a51810b1..ff21c0f634 100644
--- a/Source/NETworkManager/ViewModels/DashboardViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DashboardViewModel.cs
@@ -1,4 +1,4 @@
-namespace NETworkManager.ViewModels;
+namespace NETworkManager.ViewModels;
///
/// ViewModel for the Dashboard view.
diff --git a/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs b/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs
index b3a84ac13e..9b37597b7f 100644
--- a/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DiscoveryProtocolViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
using NETworkManager.Models.Export;
diff --git a/Source/NETworkManager/ViewModels/DropdownViewModel.cs b/Source/NETworkManager/ViewModels/DropdownViewModel.cs
index 1cd4370ef6..56dd7fd184 100644
--- a/Source/NETworkManager/ViewModels/DropdownViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DropdownViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/ExportViewModel.cs b/Source/NETworkManager/ViewModels/ExportViewModel.cs
index ddd09e332d..d9a3af287e 100644
--- a/Source/NETworkManager/ViewModels/ExportViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ExportViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
diff --git a/Source/NETworkManager/ViewModels/GroupViewModel.cs b/Source/NETworkManager/ViewModels/GroupViewModel.cs
index 9bac8b7e83..b6277d0447 100644
--- a/Source/NETworkManager/ViewModels/GroupViewModel.cs
+++ b/Source/NETworkManager/ViewModels/GroupViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Models.Network;
+using NETworkManager.Models.Network;
using NETworkManager.Models.PowerShell;
using NETworkManager.Models.PuTTY;
using NETworkManager.Models.RemoteDesktop;
diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs
index 8603f1ccd6..2ddeb4dc4c 100644
--- a/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs
+++ b/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Models.HostsFileEditor;
+using NETworkManager.Models.HostsFileEditor;
using NETworkManager.Utilities;
using System;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
index 3734abd491..d3d14ec634 100644
--- a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
+++ b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
diff --git a/Source/NETworkManager/ViewModels/IPAddressAndSubnetmaskViewModel.cs b/Source/NETworkManager/ViewModels/IPAddressAndSubnetmaskViewModel.cs
index b43336f7c3..d60463208e 100644
--- a/Source/NETworkManager/ViewModels/IPAddressAndSubnetmaskViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPAddressAndSubnetmaskViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Windows.Input;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/IPAddressViewModel.cs b/Source/NETworkManager/ViewModels/IPAddressViewModel.cs
index eaf52d7f75..5df641c3c0 100644
--- a/Source/NETworkManager/ViewModels/IPAddressViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPAddressViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Windows.Input;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs b/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs
index 1e7a56743d..1a6a7d5bc7 100644
--- a/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs
@@ -1,4 +1,4 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using System.Windows.Input;
using NETworkManager.Models.IPApi;
using NETworkManager.Settings;
diff --git a/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs b/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs
index 15ab8359dc..21d1d3cbd0 100644
--- a/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs
@@ -1,4 +1,4 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using System.Windows.Input;
using log4net;
using NETworkManager.Models.IPApi;
diff --git a/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs b/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs
index 9eb0a0f519..1900bd2f19 100644
--- a/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Models;
@@ -683,7 +683,7 @@ public void OnViewHide()
///
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.IPGeolocation_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.IPGeolocation_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -711,7 +711,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.IPGeolocation_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.IPGeolocation_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.IPGeolocation_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/IPGeolocationViewModel.cs b/Source/NETworkManager/ViewModels/IPGeolocationViewModel.cs
index ea4a691b6a..c94dfa72ef 100644
--- a/Source/NETworkManager/ViewModels/IPGeolocationViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPGeolocationViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
diff --git a/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs
index 3e03a4f207..5105db7149 100644
--- a/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Models;
@@ -692,7 +692,7 @@ public void OnViewHide()
///
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.IPScanner_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.IPScanner_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -720,7 +720,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.IPScanner_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.IPScanner_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.IPScanner_HostOrIPRange.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/IPScannerSettingsViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerSettingsViewModel.cs
index 7234d9563c..2c8664f60f 100644
--- a/Source/NETworkManager/ViewModels/IPScannerSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPScannerSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using MahApps.Metro.SimpleChildWindow;
+using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
using NETworkManager.Settings;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs
index 9f14c255ef..79b3811e9c 100644
--- a/Source/NETworkManager/ViewModels/IPScannerViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPScannerViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
diff --git a/Source/NETworkManager/ViewModels/ListenersViewModel.cs b/Source/NETworkManager/ViewModels/ListenersViewModel.cs
index a7b4fe06cc..147cd67526 100644
--- a/Source/NETworkManager/ViewModels/ListenersViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ListenersViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
diff --git a/Source/NETworkManager/ViewModels/LookupHostViewModel.cs b/Source/NETworkManager/ViewModels/LookupHostViewModel.cs
index 895700b961..5230198517 100644
--- a/Source/NETworkManager/ViewModels/LookupHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/LookupHostViewModel.cs
@@ -1,4 +1,4 @@
-namespace NETworkManager.ViewModels;
+namespace NETworkManager.ViewModels;
///
/// View model for the lookup host view.
diff --git a/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs b/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs
index 3ee09ff332..e841b129c8 100644
--- a/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs
+++ b/Source/NETworkManager/ViewModels/LookupOUILookupViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
diff --git a/Source/NETworkManager/ViewModels/LookupPortViewModel.cs b/Source/NETworkManager/ViewModels/LookupPortViewModel.cs
index 4c93491d56..056b6d6244 100644
--- a/Source/NETworkManager/ViewModels/LookupPortViewModel.cs
+++ b/Source/NETworkManager/ViewModels/LookupPortViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
diff --git a/Source/NETworkManager/ViewModels/MessageConfirmationViewModel.cs b/Source/NETworkManager/ViewModels/MessageConfirmationViewModel.cs
index 3472366e86..cbbd1f9b87 100644
--- a/Source/NETworkManager/ViewModels/MessageConfirmationViewModel.cs
+++ b/Source/NETworkManager/ViewModels/MessageConfirmationViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Utilities;
+using NETworkManager.Utilities;
using System;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/MessageViewModel.cs b/Source/NETworkManager/ViewModels/MessageViewModel.cs
index 1dc4a20c16..bf7cc88e61 100644
--- a/Source/NETworkManager/ViewModels/MessageViewModel.cs
+++ b/Source/NETworkManager/ViewModels/MessageViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Utilities;
+using NETworkManager.Utilities;
using System;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs b/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs
index afac966bef..08abc4eace 100644
--- a/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs
+++ b/Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Models.Network;
+using NETworkManager.Models.Network;
using NETworkManager.Settings;
using NETworkManager.Utilities;
using System;
diff --git a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
index 08d2a724e5..d374739b8c 100644
--- a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
+++ b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
@@ -1,4 +1,4 @@
-using LiveCharts;
+using LiveCharts;
using LiveCharts.Configurations;
using LiveCharts.Wpf;
using log4net;
@@ -1679,7 +1679,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.NetworkInterface_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.NetworkInterface_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -1702,7 +1702,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.NetworkInterface_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.NetworkInterface_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
// If no tags are selected, show all profiles
diff --git a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs
index 6cc3c66e37..453da98ccb 100644
--- a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs
@@ -1,4 +1,4 @@
-using MahApps.Metro.Controls;
+using MahApps.Metro.Controls;
using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Models;
@@ -826,7 +826,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PingMonitor_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PingMonitor_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -849,7 +849,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PingMonitor_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PingMonitor_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.PingMonitor_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs
index a8ba9b2ca7..43460623c9 100644
--- a/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PingMonitorSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Settings;
+using NETworkManager.Settings;
namespace NETworkManager.ViewModels;
diff --git a/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs
index db205fd8b3..3cb9bcb13a 100644
--- a/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PingMonitorViewModel.cs
@@ -1,4 +1,4 @@
-using LiveCharts;
+using LiveCharts;
using LiveCharts.Configurations;
using LiveCharts.Wpf;
using log4net;
diff --git a/Source/NETworkManager/ViewModels/PortProfileViewModel.cs b/Source/NETworkManager/ViewModels/PortProfileViewModel.cs
index 21c046c319..67ed27b25f 100644
--- a/Source/NETworkManager/ViewModels/PortProfileViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PortProfileViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Windows.Input;
using NETworkManager.Models.Network;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/PortProfilesViewModel.cs b/Source/NETworkManager/ViewModels/PortProfilesViewModel.cs
index 967333da4f..37da322862 100644
--- a/Source/NETworkManager/ViewModels/PortProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PortProfilesViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
diff --git a/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs
index 3b0e188816..cb6004a249 100644
--- a/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Models;
@@ -595,7 +595,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PortScanner_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PortScanner_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -618,7 +618,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PortScanner_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PortScanner_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.PortScanner_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/PortScannerSettingsViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerSettingsViewModel.cs
index 5f4ac96980..65ce5b1e58 100644
--- a/Source/NETworkManager/ViewModels/PortScannerSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PortScannerSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using MahApps.Metro.SimpleChildWindow;
+using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
using NETworkManager.Models.Network;
using NETworkManager.Settings;
diff --git a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs
index 2f0ca76e10..83b8a47aae 100644
--- a/Source/NETworkManager/ViewModels/PortScannerViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PortScannerViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
diff --git a/Source/NETworkManager/ViewModels/PowerShellConnectViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellConnectViewModel.cs
index 64e5ed9e55..0f28f371e2 100644
--- a/Source/NETworkManager/ViewModels/PowerShellConnectViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PowerShellConnectViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
diff --git a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
index 80d632bab7..1752b9f964 100644
--- a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using log4net;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
@@ -829,7 +829,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PowerShell_Enabled).SelectMany(x => x.TagsCollection).Distinct().ToList();
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PowerShell_Enabled).SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -851,7 +851,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PowerShell_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PowerShell_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.PowerShell_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/PowerShellSettingsViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellSettingsViewModel.cs
index ee1d3a6deb..23c2a90c59 100644
--- a/Source/NETworkManager/ViewModels/PowerShellSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PowerShellSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
diff --git a/Source/NETworkManager/ViewModels/ProfileFileViewModel.cs b/Source/NETworkManager/ViewModels/ProfileFileViewModel.cs
index f5a9c24ae3..559b542485 100644
--- a/Source/NETworkManager/ViewModels/ProfileFileViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ProfileFileViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Windows.Input;
using NETworkManager.Profiles;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/ProfileViewModel.cs b/Source/NETworkManager/ViewModels/ProfileViewModel.cs
index 2838d1cec2..153027f21e 100644
--- a/Source/NETworkManager/ViewModels/ProfileViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ProfileViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Controls;
+using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Models;
using NETworkManager.Models.Network;
diff --git a/Source/NETworkManager/ViewModels/ProfilesViewModel.cs b/Source/NETworkManager/ViewModels/ProfilesViewModel.cs
index 04d4ca3d90..73c0d13445 100644
--- a/Source/NETworkManager/ViewModels/ProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ProfilesViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Profiles;
+using NETworkManager.Profiles;
using NETworkManager.Settings;
using NETworkManager.Utilities;
using System;
@@ -365,7 +365,7 @@ private void SetGroupsView(GroupInfo group = null)
Groups = new CollectionViewSource
{
- Source = ProfileManager.Groups.Where(x => !x.IsDynamic).OrderBy(x => x.Name)
+ Source = ProfileManager.LoadedProfileFileData.Groups.Where(x => !x.IsDynamic).OrderBy(x => x.Name)
}.View;
// Set to null, so even when the same group is selected, the profiles get refreshed
@@ -384,7 +384,7 @@ private void SetGroupsView(GroupInfo group = null)
private void CreateTags()
{
// Get all tags from profiles in the selected group
- var tags = ProfileManager.Groups.First(x => x.Name == SelectedGroup.Name).Profiles
+ var tags = ProfileManager.LoadedProfileFileData.Groups.First(x => x.Name == SelectedGroup.Name).Profiles
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -407,7 +407,7 @@ private void SetProfilesView(ProfileFilterInfo filter, GroupInfo group, ProfileI
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.FirstOrDefault(x => x.Equals(group))?.Profiles.Where(x => !x.IsDynamic && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.FirstOrDefault(x => x.Equals(group))?.Profiles.Where(x => !x.IsDynamic && (
string.IsNullOrEmpty(Search) || x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
// If no tags are selected, show all profiles
(!filter.Tags.Any()) ||
diff --git a/Source/NETworkManager/ViewModels/PuTTYConnectViewModel.cs b/Source/NETworkManager/ViewModels/PuTTYConnectViewModel.cs
index 1c71d7aac1..f552308057 100644
--- a/Source/NETworkManager/ViewModels/PuTTYConnectViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PuTTYConnectViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs b/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs
index a3a8313eb7..e3b26a5a3d 100644
--- a/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using log4net;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
@@ -835,7 +835,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PuTTY_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PuTTY_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -858,7 +858,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.PuTTY_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.PuTTY_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.PuTTY_HostOrSerialLine.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/PuTTYSettingsViewModel.cs b/Source/NETworkManager/ViewModels/PuTTYSettingsViewModel.cs
index 78ced6a3e9..fe1f5ed9f3 100644
--- a/Source/NETworkManager/ViewModels/PuTTYSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PuTTYSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
diff --git a/Source/NETworkManager/ViewModels/RemoteDesktopConnectViewModel.cs b/Source/NETworkManager/ViewModels/RemoteDesktopConnectViewModel.cs
index fc445710e0..e1815bc386 100644
--- a/Source/NETworkManager/ViewModels/RemoteDesktopConnectViewModel.cs
+++ b/Source/NETworkManager/ViewModels/RemoteDesktopConnectViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Settings;
+using NETworkManager.Settings;
using NETworkManager.Utilities;
using System;
using System.ComponentModel;
diff --git a/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs b/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs
index 5a9a5b9612..9b2a232621 100644
--- a/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
@@ -721,7 +721,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.RemoteDesktop_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.RemoteDesktop_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -744,7 +744,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.RemoteDesktop_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.RemoteDesktop_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.RemoteDesktop_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/RemoteDesktopSettingsViewModel.cs b/Source/NETworkManager/ViewModels/RemoteDesktopSettingsViewModel.cs
index 925772e2ec..9f5c050f9f 100644
--- a/Source/NETworkManager/ViewModels/RemoteDesktopSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/RemoteDesktopSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using NETworkManager.Models.RemoteDesktop;
diff --git a/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs b/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs
index 972d7bfaa0..e17bacebc4 100644
--- a/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Models;
@@ -505,7 +505,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.SNMP_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.SNMP_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -528,7 +528,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.SNMP_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.SNMP_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.SNMP_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/SNMPOIDProfileViewModel.cs b/Source/NETworkManager/ViewModels/SNMPOIDProfileViewModel.cs
index 499a12590b..e293f9a243 100644
--- a/Source/NETworkManager/ViewModels/SNMPOIDProfileViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SNMPOIDProfileViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/SNMPOIDProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SNMPOIDProfilesViewModel.cs
index 8eb692521d..028de1bb18 100644
--- a/Source/NETworkManager/ViewModels/SNMPOIDProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SNMPOIDProfilesViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
diff --git a/Source/NETworkManager/ViewModels/SNMPSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SNMPSettingsViewModel.cs
index 124ebcbbcb..f22d6fa98e 100644
--- a/Source/NETworkManager/ViewModels/SNMPSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SNMPSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using Lextm.SharpSnmpLib.Messaging;
+using Lextm.SharpSnmpLib.Messaging;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
using NETworkManager.Models.Network;
diff --git a/Source/NETworkManager/ViewModels/SNMPViewModel.cs b/Source/NETworkManager/ViewModels/SNMPViewModel.cs
index b255d60b0e..d95b96297c 100644
--- a/Source/NETworkManager/ViewModels/SNMPViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SNMPViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
diff --git a/Source/NETworkManager/ViewModels/SNTPLookupHostViewModel.cs b/Source/NETworkManager/ViewModels/SNTPLookupHostViewModel.cs
index b41e2c72c5..ceee731192 100644
--- a/Source/NETworkManager/ViewModels/SNTPLookupHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SNTPLookupHostViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Dragablz;
diff --git a/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs
index 305dc0e105..b3c916fdda 100644
--- a/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SNTPLookupSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using MahApps.Metro.SimpleChildWindow;
+using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
using NETworkManager.Models.Network;
using NETworkManager.Settings;
diff --git a/Source/NETworkManager/ViewModels/SNTPLookupViewModel.cs b/Source/NETworkManager/ViewModels/SNTPLookupViewModel.cs
index dd0b8de56c..cddb032164 100644
--- a/Source/NETworkManager/ViewModels/SNTPLookupViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SNTPLookupViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
diff --git a/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs b/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs
index 70069dbb78..a268027722 100644
--- a/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ServerConnectionInfoProfileViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
diff --git a/Source/NETworkManager/ViewModels/SettingsAppearanceViewModel.cs b/Source/NETworkManager/ViewModels/SettingsAppearanceViewModel.cs
index cd82cf16af..6b3ca40fcb 100644
--- a/Source/NETworkManager/ViewModels/SettingsAppearanceViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsAppearanceViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Models.Appearance;
+using NETworkManager.Models.Appearance;
using NETworkManager.Settings;
using System.ComponentModel;
using System.Linq;
diff --git a/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs b/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs
index 55dc1a5b1d..9d91a126b1 100644
--- a/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Threading.Tasks;
using System.Windows;
using NETworkManager.Localization.Resources;
diff --git a/Source/NETworkManager/ViewModels/SettingsGeneralViewModel.cs b/Source/NETworkManager/ViewModels/SettingsGeneralViewModel.cs
index 817a4a6590..5cfa75f18b 100644
--- a/Source/NETworkManager/ViewModels/SettingsGeneralViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsGeneralViewModel.cs
@@ -1,4 +1,4 @@
-using System.ComponentModel;
+using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/SettingsHotKeysViewModel.cs b/Source/NETworkManager/ViewModels/SettingsHotKeysViewModel.cs
index aa46309139..363618e622 100644
--- a/Source/NETworkManager/ViewModels/SettingsHotKeysViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsHotKeysViewModel.cs
@@ -1,4 +1,4 @@
-using MahApps.Metro.Controls;
+using MahApps.Metro.Controls;
using NETworkManager.Settings;
using NETworkManager.Utilities;
diff --git a/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs b/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs
index ef6213c807..392608bbe5 100644
--- a/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
diff --git a/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs b/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs
index d9dcb246fd..87efadeb7e 100644
--- a/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsNetworkViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Settings;
+using NETworkManager.Settings;
namespace NETworkManager.ViewModels;
diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
index 7dd0e17546..e2c58d980d 100644
--- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
@@ -1,4 +1,4 @@
-using MahApps.Metro.SimpleChildWindow;
+using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
using NETworkManager.Profiles;
using NETworkManager.Settings;
diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
index dc1d7d95b1..7e15ada0af 100644
--- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Localization.Resources;
+using NETworkManager.Localization.Resources;
using NETworkManager.Settings;
using NETworkManager.Utilities;
using System;
diff --git a/Source/NETworkManager/ViewModels/SettingsStatusViewModel.cs b/Source/NETworkManager/ViewModels/SettingsStatusViewModel.cs
index 6cfe5bab66..fbcaf7b473 100644
--- a/Source/NETworkManager/ViewModels/SettingsStatusViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsStatusViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Settings;
+using NETworkManager.Settings;
namespace NETworkManager.ViewModels;
diff --git a/Source/NETworkManager/ViewModels/SettingsUpdateViewModel.cs b/Source/NETworkManager/ViewModels/SettingsUpdateViewModel.cs
index 204ed9f7f0..b760064e6f 100644
--- a/Source/NETworkManager/ViewModels/SettingsUpdateViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsUpdateViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Settings;
+using NETworkManager.Settings;
namespace NETworkManager.ViewModels;
diff --git a/Source/NETworkManager/ViewModels/SettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsViewModel.cs
index a28445fd9d..e7a668933b 100644
--- a/Source/NETworkManager/ViewModels/SettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
diff --git a/Source/NETworkManager/ViewModels/SettingsWindowViewModel.cs b/Source/NETworkManager/ViewModels/SettingsWindowViewModel.cs
index ce01416562..0e2fb9ef70 100644
--- a/Source/NETworkManager/ViewModels/SettingsWindowViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsWindowViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Settings;
+using NETworkManager.Settings;
namespace NETworkManager.ViewModels;
diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorCalculatorViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorCalculatorViewModel.cs
index 59b51ebcbc..0a2dc2544a 100644
--- a/Source/NETworkManager/ViewModels/SubnetCalculatorCalculatorViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SubnetCalculatorCalculatorViewModel.cs
@@ -1,4 +1,4 @@
-using System.ComponentModel;
+using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Windows;
diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorHostViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorHostViewModel.cs
index eddb327af5..f037c6a6aa 100644
--- a/Source/NETworkManager/ViewModels/SubnetCalculatorHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SubnetCalculatorHostViewModel.cs
@@ -1,4 +1,4 @@
-namespace NETworkManager.ViewModels;
+namespace NETworkManager.ViewModels;
public class SubnetCalculatorHostViewModel : ViewModelBase
{
diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs
index b1845036f0..67942da2dc 100644
--- a/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SubnetCalculatorSubnettingViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Localization.Resources;
diff --git a/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs b/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs
index 59b1ab7d1c..02fbdeff31 100644
--- a/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SubnetCalculatorWideSubnetViewModel.cs
@@ -1,4 +1,4 @@
-using System.ComponentModel;
+using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Windows;
diff --git a/Source/NETworkManager/ViewModels/TigerVNCConnectViewModel.cs b/Source/NETworkManager/ViewModels/TigerVNCConnectViewModel.cs
index b0e70c09ca..4a14b67b72 100644
--- a/Source/NETworkManager/ViewModels/TigerVNCConnectViewModel.cs
+++ b/Source/NETworkManager/ViewModels/TigerVNCConnectViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs b/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs
index 5f348ad5bb..bd6e7f1883 100644
--- a/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
@@ -630,7 +630,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.TigerVNC_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.TigerVNC_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -653,7 +653,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.TigerVNC_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.TigerVNC_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.TigerVNC_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/TigerVNCSettingsViewModel.cs b/Source/NETworkManager/ViewModels/TigerVNCSettingsViewModel.cs
index b2592bcc9b..316ba7e411 100644
--- a/Source/NETworkManager/ViewModels/TigerVNCSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/TigerVNCSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
diff --git a/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs b/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs
index cd29d858ed..e5a8eea5dc 100644
--- a/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Models;
@@ -490,7 +490,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.Traceroute_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.Traceroute_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -513,7 +513,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.Traceroute_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.Traceroute_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.Traceroute_Host.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/TracerouteSettingsViewModel.cs b/Source/NETworkManager/ViewModels/TracerouteSettingsViewModel.cs
index e78f33264c..195871d3f8 100644
--- a/Source/NETworkManager/ViewModels/TracerouteSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/TracerouteSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Settings;
+using NETworkManager.Settings;
namespace NETworkManager.ViewModels;
diff --git a/Source/NETworkManager/ViewModels/TracerouteViewModel.cs b/Source/NETworkManager/ViewModels/TracerouteViewModel.cs
index c6801843c6..08993c855b 100644
--- a/Source/NETworkManager/ViewModels/TracerouteViewModel.cs
+++ b/Source/NETworkManager/ViewModels/TracerouteViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
diff --git a/Source/NETworkManager/ViewModels/UpgradeViewModel.cs b/Source/NETworkManager/ViewModels/UpgradeViewModel.cs
index 8eaa9342e0..3c5ea6956a 100644
--- a/Source/NETworkManager/ViewModels/UpgradeViewModel.cs
+++ b/Source/NETworkManager/ViewModels/UpgradeViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Documentation;
+using NETworkManager.Documentation;
using NETworkManager.Settings;
using NETworkManager.Utilities;
using System;
diff --git a/Source/NETworkManager/ViewModels/ViewModelBase.cs b/Source/NETworkManager/ViewModels/ViewModelBase.cs
index 705c08e0ec..e1c708d22f 100644
--- a/Source/NETworkManager/ViewModels/ViewModelBase.cs
+++ b/Source/NETworkManager/ViewModels/ViewModelBase.cs
@@ -1,4 +1,4 @@
-using System.Windows.Input;
+using System.Windows.Input;
using NETworkManager.Utilities;
namespace NETworkManager.ViewModels;
diff --git a/Source/NETworkManager/ViewModels/WakeOnLANSettingsViewModel.cs b/Source/NETworkManager/ViewModels/WakeOnLANSettingsViewModel.cs
index 5510300a55..5f33fb1219 100644
--- a/Source/NETworkManager/ViewModels/WakeOnLANSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WakeOnLANSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Settings;
+using NETworkManager.Settings;
namespace NETworkManager.ViewModels;
diff --git a/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs b/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs
index e450f3aee9..4e4ed4c4ec 100644
--- a/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs
@@ -1,4 +1,4 @@
-using MahApps.Metro.Controls;
+using MahApps.Metro.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Models;
using NETworkManager.Models.Network;
@@ -659,7 +659,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.WakeOnLAN_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.WakeOnLAN_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -682,7 +682,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.WakeOnLAN_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.WakeOnLAN_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.WakeOnLAN_MACAddress.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/WebConsoleConnectViewModel.cs b/Source/NETworkManager/ViewModels/WebConsoleConnectViewModel.cs
index 52d885b583..45277a9486 100644
--- a/Source/NETworkManager/ViewModels/WebConsoleConnectViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WebConsoleConnectViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs b/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs
index 9eecebcbf7..dc04adceb3 100644
--- a/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using MahApps.Metro.SimpleChildWindow;
using Microsoft.Web.WebView2.Core;
using NETworkManager.Controls;
@@ -598,7 +598,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.WebConsole_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.WebConsole_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -621,7 +621,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.WebConsole_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.WebConsole_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.WebConsole_Url.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/WebConsoleSettingsViewModel.cs b/Source/NETworkManager/ViewModels/WebConsoleSettingsViewModel.cs
index b5f3b9230b..b23100bb4f 100644
--- a/Source/NETworkManager/ViewModels/WebConsoleSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WebConsoleSettingsViewModel.cs
@@ -1,4 +1,4 @@
-using MahApps.Metro.SimpleChildWindow;
+using MahApps.Metro.SimpleChildWindow;
using Microsoft.Web.WebView2.Core;
using NETworkManager.Localization.Resources;
using NETworkManager.Settings;
diff --git a/Source/NETworkManager/ViewModels/WelcomeViewModel.cs b/Source/NETworkManager/ViewModels/WelcomeViewModel.cs
index 6c2f9dde56..f2466c31e9 100644
--- a/Source/NETworkManager/ViewModels/WelcomeViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WelcomeViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Settings;
+using NETworkManager.Settings;
using NETworkManager.Utilities;
using System;
using System.Windows.Input;
diff --git a/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs b/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs
index d061001019..66e6c416ea 100644
--- a/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs
@@ -1,4 +1,4 @@
-using Dragablz;
+using Dragablz;
using NETworkManager.Controls;
using NETworkManager.Localization.Resources;
using NETworkManager.Models;
@@ -484,7 +484,7 @@ public void OnViewHide()
private void CreateTags()
{
- var tags = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.Whois_Enabled)
+ var tags = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.Whois_Enabled)
.SelectMany(x => x.TagsCollection).Distinct().ToList();
var tagSet = new HashSet(tags);
@@ -507,7 +507,7 @@ private void SetProfilesView(ProfileFilterInfo filter, ProfileInfo profile = nul
{
Profiles = new CollectionViewSource
{
- Source = ProfileManager.Groups.SelectMany(x => x.Profiles).Where(x => x.Whois_Enabled && (
+ Source = ProfileManager.LoadedProfileFileData.Groups.SelectMany(x => x.Profiles).Where(x => x.Whois_Enabled && (
string.IsNullOrEmpty(filter.Search) ||
x.Name.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1 ||
x.Whois_Domain.IndexOf(filter.Search, StringComparison.OrdinalIgnoreCase) > -1) && (
diff --git a/Source/NETworkManager/ViewModels/WhoisViewModel.cs b/Source/NETworkManager/ViewModels/WhoisViewModel.cs
index 8740db96dc..fe4525032c 100644
--- a/Source/NETworkManager/ViewModels/WhoisViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WhoisViewModel.cs
@@ -1,4 +1,4 @@
-using log4net;
+using log4net;
using MahApps.Metro.Controls;
using MahApps.Metro.SimpleChildWindow;
using NETworkManager.Controls;
diff --git a/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs b/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs
index c2e7697be2..dda145a34a 100644
--- a/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs
@@ -1,4 +1,4 @@
-using NETworkManager.Models.Network;
+using NETworkManager.Models.Network;
using NETworkManager.Utilities;
using NETworkManager.Settings;
using System;
diff --git a/Source/NETworkManager/ViewModels/WiFiViewModel.cs b/Source/NETworkManager/ViewModels/WiFiViewModel.cs
index f1c80913f4..0115605412 100644
--- a/Source/NETworkManager/ViewModels/WiFiViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WiFiViewModel.cs
@@ -1,4 +1,4 @@
-using LiveCharts;
+using LiveCharts;
using LiveCharts.Wpf;
using log4net;
using MahApps.Metro.SimpleChildWindow;
From db0d084aaea9df391c207f91edd3d599e2290177 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 1 Feb 2026 02:03:23 +0100
Subject: [PATCH 06/10] Docs: #3302
---
Website/docs/changelog/next-release.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index bf65f5504a..5995a082e8 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -31,11 +31,11 @@ Release date: **xx.xx.2025**
- Profile and settings files have been migrated from `XML` to `JSON`. Existing files will be automatically converted to `JSON` on first load after the update. [#3282](https://github.com/BornToBeRoot/NETworkManager/pull/3282) [#3299](https://github.com/BornToBeRoot/NETworkManager/pull/3299)
- :::info
+ :::note
Starting with this release, new profile and settings files are created in `JSON` format. Existing `XML` files will be converted automatically on first load after upgrading. Automatic support for the migration will be provided until at least `2027`; after that only `JSON` files will be supported and very old installations may require an interim update.
- The migration process creates a backup of the original files in the `Backups` subfolder of the settings and profiles directories. You can restore the originals from that folder if needed, but it's recommended to make a separate backup of your profile and settings files before updating.
+ The migration process creates a backup of the original files in the `Backups` subfolder of the settings and profiles directories. You can restore the originals from that folder if needed (they will work with the privous version), but it's **recommended to make a separate backup of your profile and settings files before updating**. If you encounter any issues during or after the migration, please report them via the [issue tracker](https://github.com/BornToBeRoot/NETworkManager/issues/new/choose).
:::
@@ -58,6 +58,7 @@ Release date: **xx.xx.2025**
**Profiles**
- Profile file format migrated from `XML` to `JSON`. The profile file will be automatically converted on first load after the update. [#3299](https://github.com/BornToBeRoot/NETworkManager/pull/3299)
+- Create a daily backup of profile files before saving changes. By default, up to `10` backup files are kept in the `Backups` subfolder of the profiles directory. [#3302](https://github.com/BornToBeRoot/NETworkManager/pull/3302)
- Profile file creation flow improved — when adding a new profile you are now prompted to enable profile-file encryption to protect stored credentials and settings. [#3227](https://github.com/BornToBeRoot/NETworkManager/pull/3227)
- Profile file dialog migrated to a child window to improve usability. [#3227](https://github.com/BornToBeRoot/NETworkManager/pull/3227)
- Credential dialogs migrated to child windows to improve usability. [#3231](https://github.com/BornToBeRoot/NETworkManager/pull/3231)
From 042dc06fd5fc305009274b1cbda3d73116f8e37b Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 1 Feb 2026 02:05:40 +0100
Subject: [PATCH 07/10] Update Source/NETworkManager.Settings/SettingsInfo.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
Source/NETworkManager.Settings/SettingsInfo.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index 2d96124243..97834d7515 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -51,7 +51,7 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
///
/// Determines if the welcome dialog should be shown on application start.
- /// S
+ ///
public bool WelcomeDialog_Show
{
get => _welcomeDialog_Show;
From a88100c55ea56b3e814fb04f1e508899a9a32ae4 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 1 Feb 2026 02:15:37 +0100
Subject: [PATCH 08/10] Fix: Wrong behaviour if null
---
Source/NETworkManager.Profiles/ProfileManager.cs | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/Source/NETworkManager.Profiles/ProfileManager.cs b/Source/NETworkManager.Profiles/ProfileManager.cs
index 9963e88f05..29ff30c471 100644
--- a/Source/NETworkManager.Profiles/ProfileManager.cs
+++ b/Source/NETworkManager.Profiles/ProfileManager.cs
@@ -1084,10 +1084,18 @@ public static bool GroupExists(string name)
///
/// Name of the group
/// True if the group has no profiles.
+ /// Thrown when name is null or empty.
+ /// Thrown when group with specified name is not found.
public static bool IsGroupEmpty(string name)
{
+ ArgumentException.ThrowIfNullOrWhiteSpace(name);
+
var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name == name);
- return group?.Profiles.Count == 0;
+
+ if (group == null)
+ throw new InvalidOperationException($"Group '{name}' not found.");
+
+ return group.Profiles.Count == 0;
}
#endregion
From 1db52f49ab13aefd158cc5eebe96434ef267c3f5 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sun, 1 Feb 2026 02:16:02 +0100
Subject: [PATCH 09/10] Fix XAML formatting: consolidate Button closing tag to
single line (#3308)
* Initial plan
* Fix: Consolidate Button closing tag to single line
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
---
Source/NETworkManager/Views/SettingsSettingsView.xaml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/Source/NETworkManager/Views/SettingsSettingsView.xaml b/Source/NETworkManager/Views/SettingsSettingsView.xaml
index c4880b397e..57a2a964b5 100644
--- a/Source/NETworkManager/Views/SettingsSettingsView.xaml
+++ b/Source/NETworkManager/Views/SettingsSettingsView.xaml
@@ -51,7 +51,6 @@
+ Style="{StaticResource DefaultButton}" HorizontalAlignment="Left" />
\ No newline at end of file
From bbd3e8a61d5aa31a3bd25aa73fa29d17d27aae9c Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Sun, 1 Feb 2026 02:16:14 +0100
Subject: [PATCH 10/10] Remove trailing whitespace from
SettingsSettingsView.xaml (#3307)
* Initial plan
* Remove trailing whitespace from SettingsSettingsView.xaml line 40
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
---
Source/NETworkManager/Views/SettingsSettingsView.xaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/NETworkManager/Views/SettingsSettingsView.xaml b/Source/NETworkManager/Views/SettingsSettingsView.xaml
index 57a2a964b5..b51f7fe9e6 100644
--- a/Source/NETworkManager/Views/SettingsSettingsView.xaml
+++ b/Source/NETworkManager/Views/SettingsSettingsView.xaml
@@ -37,7 +37,7 @@
-
+