From cfa7ef642ca982f5126b21fde055cb38390eaf33 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 22:38:48 +0000 Subject: [PATCH 01/15] Initial plan From 26587882bdfdb7014767be5e07449fe964e11709 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 22:49:41 +0000 Subject: [PATCH 02/15] Add profile file location management with policy support and rename folder location properties Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> --- .../Resources/Strings.Designer.cs | 22 +++ .../Resources/Strings.resx | 10 + .../NETworkManager.Profiles/ProfileManager.cs | 115 ++++++++++- .../LocalSettingsInfo.cs | 12 +- Source/NETworkManager.Settings/PolicyInfo.cs | 7 +- .../NETworkManager.Settings/PolicyManager.cs | 3 +- .../NETworkManager.Settings/SettingsInfo.cs | 19 ++ .../SettingsManager.cs | 22 +-- .../config.json.example | 3 +- .../ViewModels/SettingsProfilesViewModel.cs | 141 ++++++++++++++ .../ViewModels/SettingsSettingsViewModel.cs | 6 +- .../Views/SettingsProfilesView.xaml | 184 ++++++++++++++++-- .../Views/SettingsProfilesView.xaml.cs | 17 ++ Website/docs/changelog/next-release.md | 5 + Website/docs/settings/profiles.md | 24 +++ Website/docs/settings/settings.md | 4 +- Website/docs/system-wide-policies.md | 2 +- 17 files changed, 544 insertions(+), 52 deletions(-) diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs index 40ef350d30..b431a282ac 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs +++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs @@ -1475,6 +1475,17 @@ public static string ChangeLocationSettingsMessage { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die The location is changed and the application is restarted afterwards. + /// + ///You can copy your profile files from "{0}" to "{1}" to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt. + /// + public static string ChangeLocationProfilesMessage { + get { + return ResourceManager.GetString("ChangeLocationProfilesMessage", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Changelog ähnelt. /// @@ -8902,6 +8913,17 @@ public static string RestoreDefaultLocationSettingsMessage { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die The default path is restored and the application is restarted afterwards. + /// + ///You can copy your profile files from "{0}" to "{1}" to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt. + /// + public static string RestoreDefaultLocationProfilesMessage { + get { + return ResourceManager.GetString("RestoreDefaultLocationProfilesMessage", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Restore defaults ähnelt. /// diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx index ad2eb07c93..550f519f38 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.resx +++ b/Source/NETworkManager.Localization/Resources/Strings.resx @@ -3988,4 +3988,14 @@ You can copy the “settings.json” file from "{0}" to "{1}" to migrate your pr Enter a valid folder path! + + The location is changed and the application is restarted afterwards. + +You can copy your profile files from “{0}” to “{1}” to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. + + + The default path is restored and the application is restarted afterwards. + +You can copy your profile files from “{0}” to “{1}” to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. + \ No newline at end of file diff --git a/Source/NETworkManager.Profiles/ProfileManager.cs b/Source/NETworkManager.Profiles/ProfileManager.cs index bbb62fa382..6978c51d45 100644 --- a/Source/NETworkManager.Profiles/ProfileManager.cs +++ b/Source/NETworkManager.Profiles/ProfileManager.cs @@ -197,14 +197,121 @@ private static void ProfilesUpdated(bool profilesChanged = true) /// /// Method to get the path of the profiles folder. + /// Priority: 1. Policy override, 2. Custom user path (from SettingsInfo), 3. Portable/default. /// /// Path to the profiles folder. public static string GetProfilesFolderLocation() { - return ConfigurationManager.Current.IsPortable - ? Path.Combine(AssemblyManager.Current.Location, ProfilesFolderName) - : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - AssemblyManager.Current.Name, ProfilesFolderName); + // 1. Policy override takes precedence (for IT administrators) + if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.Profiles_FolderLocation)) + { + var validatedPath = ValidateProfilesFolderPath( + PolicyManager.Current.Profiles_FolderLocation, + "Policy-provided", + "next priority"); + + if (validatedPath != null) + return validatedPath; + } + + // 2. Custom user-configured path (not available in portable mode) + if (!ConfigurationManager.Current.IsPortable && + !string.IsNullOrWhiteSpace(SettingsManager.Current?.Profiles_CustomProfilesFolderLocation)) + { + var validatedPath = ValidateProfilesFolderPath( + SettingsManager.Current.Profiles_CustomProfilesFolderLocation, + "Custom", + "default location"); + + if (validatedPath != null) + return validatedPath; + } + + // 3. Fall back to portable or default location + if (ConfigurationManager.Current.IsPortable) + return GetPortableProfilesFolderLocation(); + else + return GetDefaultProfilesFolderLocation(); + } + + /// + /// Method to get the default profiles folder location in the user's Documents directory. + /// + /// Path to the default profiles folder location. + public static string GetDefaultProfilesFolderLocation() + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + AssemblyManager.Current.Name, ProfilesFolderName); + } + + /// + /// Method to get the portable profiles folder location (in the same directory as the application). + /// + /// Path to the portable profiles folder location. + public static string GetPortableProfilesFolderLocation() + { + return Path.Combine(AssemblyManager.Current.Location, ProfilesFolderName); + } + + /// + /// Validates a profiles folder path for correctness and accessibility. + /// + /// The path to validate. + /// Description of the path source for logging (e.g., "Policy-provided", "Custom"). + /// Message describing what happens on validation failure (e.g., "next priority", "default location"). + /// The validated full path if valid; otherwise, null. + private static string ValidateProfilesFolderPath(string path, string pathSource, string fallbackMessage) + { + // Expand environment variables first (e.g. %userprofile%\profiles -> C:\Users\...\profiles) + path = Environment.ExpandEnvironmentVariables(path); + + // Validate that the path is rooted (absolute) + if (!Path.IsPathRooted(path)) + { + Log.Error($"{pathSource} Profiles_FolderLocation is not an absolute path: {path}. Falling back to {fallbackMessage}."); + return null; + } + + // Validate that the path doesn't contain invalid characters + try + { + // This will throw ArgumentException, NotSupportedException, SecurityException, PathTooLongException, or IOException if the path is invalid + var fullPath = Path.GetFullPath(path); + + // Check if the path is a directory (not a file) + if (File.Exists(fullPath)) + { + Log.Error($"{pathSource} Profiles_FolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}."); + return null; + } + + return Path.TrimEndingDirectorySeparator(fullPath); + } + catch (ArgumentException ex) + { + Log.Error($"{pathSource} Profiles_FolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex); + return null; + } + catch (NotSupportedException ex) + { + Log.Error($"{pathSource} Profiles_FolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex); + return null; + } + catch (SecurityException ex) + { + Log.Error($"Insufficient permissions to access {pathSource} Profiles_FolderLocation: {path}. Falling back to {fallbackMessage}.", ex); + return null; + } + catch (PathTooLongException ex) + { + Log.Error($"{pathSource} Profiles_FolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex); + return null; + } + catch (IOException ex) + { + Log.Error($"{pathSource} Profiles_FolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex); + return null; + } } /// diff --git a/Source/NETworkManager.Settings/LocalSettingsInfo.cs b/Source/NETworkManager.Settings/LocalSettingsInfo.cs index 6c7d1a692f..b3388d396e 100644 --- a/Source/NETworkManager.Settings/LocalSettingsInfo.cs +++ b/Source/NETworkManager.Settings/LocalSettingsInfo.cs @@ -34,23 +34,23 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null) [JsonIgnore] public bool SettingsChanged { get; set; } /// - /// Private field for the property." + /// Private field for the property." /// - private string _settingsFolderLocation; + private string _settings_FolderLocation; /// /// Location of the folder where the local settings file is stored. /// This can be changed by the user to move the settings file to a different location. /// - public string SettingsFolderLocation + public string Settings_FolderLocation { - get => _settingsFolderLocation; + get => _settings_FolderLocation; set { - if (_settingsFolderLocation == value) + if (_settings_FolderLocation == value) return; - _settingsFolderLocation = value; + _settings_FolderLocation = value; OnPropertyChanged(); } } diff --git a/Source/NETworkManager.Settings/PolicyInfo.cs b/Source/NETworkManager.Settings/PolicyInfo.cs index 2f17ee8abf..55d6767084 100644 --- a/Source/NETworkManager.Settings/PolicyInfo.cs +++ b/Source/NETworkManager.Settings/PolicyInfo.cs @@ -11,6 +11,9 @@ public class PolicyInfo [JsonPropertyName("Update_CheckForUpdatesAtStartup")] public bool? Update_CheckForUpdatesAtStartup { get; set; } - [JsonPropertyName("SettingsFolderLocation")] - public string? SettingsFolderLocation { get; set; } + [JsonPropertyName("Settings_FolderLocation")] + public string? Settings_FolderLocation { get; set; } + + [JsonPropertyName("Profiles_FolderLocation")] + public string? Profiles_FolderLocation { get; set; } } diff --git a/Source/NETworkManager.Settings/PolicyManager.cs b/Source/NETworkManager.Settings/PolicyManager.cs index ddbdfd3d38..26063ac16b 100644 --- a/Source/NETworkManager.Settings/PolicyManager.cs +++ b/Source/NETworkManager.Settings/PolicyManager.cs @@ -84,7 +84,8 @@ public static void Load() // Log enabled settings Log.Info($"System-wide policy - Update_CheckForUpdatesAtStartup: {Current.Update_CheckForUpdatesAtStartup?.ToString() ?? "Not set"}"); - Log.Info($"System-wide policy - SettingsFolderLocation: {Current.SettingsFolderLocation ?? "Not set"}"); + Log.Info($"System-wide policy - Settings_FolderLocation: {Current.Settings_FolderLocation ?? "Not set"}"); + Log.Info($"System-wide policy - Profiles_FolderLocation: {Current.Profiles_FolderLocation ?? "Not set"}"); } } catch (Exception ex) diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs index 5e810c8ec3..0bbf9d31f1 100644 --- a/Source/NETworkManager.Settings/SettingsInfo.cs +++ b/Source/NETworkManager.Settings/SettingsInfo.cs @@ -622,6 +622,25 @@ public int Profiles_MaximumNumberOfBackups } } + private string _profiles_CustomProfilesFolderLocation; + + /// + /// Custom profiles folder location set by the user. + /// When set, overrides the default profiles folder location. + /// + public string Profiles_CustomProfilesFolderLocation + { + get => _profiles_CustomProfilesFolderLocation; + set + { + if (value == _profiles_CustomProfilesFolderLocation) + return; + + _profiles_CustomProfilesFolderLocation = value; + OnPropertyChanged(); + } + } + // Settings private bool _settings_IsDailyBackupEnabled = GlobalStaticConfiguration.Settings_IsDailyBackupEnabled; diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs index 8a289a632d..6852016be9 100644 --- a/Source/NETworkManager.Settings/SettingsManager.cs +++ b/Source/NETworkManager.Settings/SettingsManager.cs @@ -79,10 +79,10 @@ public static class SettingsManager public static string GetSettingsFolderLocation() { // 1. Policy override takes precedence (for IT administrators) - if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.SettingsFolderLocation)) + if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.Settings_FolderLocation)) { var validatedPath = ValidateSettingsFolderPath( - PolicyManager.Current.SettingsFolderLocation, + PolicyManager.Current.Settings_FolderLocation, "Policy-provided", "next priority"); @@ -92,10 +92,10 @@ public static string GetSettingsFolderLocation() // 2. Custom user-configured path (not available in portable mode) if (!ConfigurationManager.Current.IsPortable && - !string.IsNullOrWhiteSpace(LocalSettingsManager.Current?.SettingsFolderLocation)) + !string.IsNullOrWhiteSpace(LocalSettingsManager.Current?.Settings_FolderLocation)) { var validatedPath = ValidateSettingsFolderPath( - LocalSettingsManager.Current.SettingsFolderLocation, + LocalSettingsManager.Current.Settings_FolderLocation, "Custom", "default location"); @@ -144,7 +144,7 @@ private static string ValidateSettingsFolderPath(string path, string pathSource, // Validate that the path is rooted (absolute) if (!Path.IsPathRooted(path)) { - Log.Error($"{pathSource} SettingsFolderLocation is not an absolute path: {path}. Falling back to {fallbackMessage}."); + Log.Error($"{pathSource} Settings_FolderLocation is not an absolute path: {path}. Falling back to {fallbackMessage}."); return null; } @@ -157,7 +157,7 @@ private static string ValidateSettingsFolderPath(string path, string pathSource, // Check if the path is a directory (not a file) if (File.Exists(fullPath)) { - Log.Error($"{pathSource} SettingsFolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}."); + Log.Error($"{pathSource} Settings_FolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}."); return null; } @@ -165,27 +165,27 @@ private static string ValidateSettingsFolderPath(string path, string pathSource, } catch (ArgumentException ex) { - Log.Error($"{pathSource} SettingsFolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex); + Log.Error($"{pathSource} Settings_FolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex); return null; } catch (NotSupportedException ex) { - Log.Error($"{pathSource} SettingsFolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex); + Log.Error($"{pathSource} Settings_FolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex); return null; } catch (SecurityException ex) { - Log.Error($"Insufficient permissions to access {pathSource} SettingsFolderLocation: {path}. Falling back to {fallbackMessage}.", ex); + Log.Error($"Insufficient permissions to access {pathSource} Settings_FolderLocation: {path}. Falling back to {fallbackMessage}.", ex); return null; } catch (PathTooLongException ex) { - Log.Error($"{pathSource} SettingsFolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex); + Log.Error($"{pathSource} Settings_FolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex); return null; } catch (IOException ex) { - Log.Error($"{pathSource} SettingsFolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex); + Log.Error($"{pathSource} Settings_FolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex); return null; } } diff --git a/Source/NETworkManager.Settings/config.json.example b/Source/NETworkManager.Settings/config.json.example index 0a47ffa700..3747efaeaa 100644 --- a/Source/NETworkManager.Settings/config.json.example +++ b/Source/NETworkManager.Settings/config.json.example @@ -1,4 +1,5 @@ { "Update_CheckForUpdatesAtStartup": false, - "SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings" + "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings", + "Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles" } \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs index e2c58d980d..c9efb56184 100644 --- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs @@ -7,6 +7,7 @@ using System; using System.ComponentModel; using System.Diagnostics; +using System.IO; using System.Linq; using System.Security.Cryptography; using System.Threading.Tasks; @@ -34,11 +35,55 @@ public string Location if (value == _location) return; + if (!_isLoading) + IsLocationChanged = !string.Equals(value, ProfileManager.GetProfilesFolderLocation(), StringComparison.OrdinalIgnoreCase); + _location = value; OnPropertyChanged(); } } + /// + /// Indicates whether the profiles location is managed by a system-wide policy. + /// + public bool IsLocationManagedByPolicy => !string.IsNullOrWhiteSpace(PolicyManager.Current?.Profiles_FolderLocation); + + private bool _isLocationChanged; + + /// + /// Gets or sets a value indicating whether the location has changed. + /// + public bool IsLocationChanged + { + get => _isLocationChanged; + set + { + if (value == _isLocationChanged) + return; + + _isLocationChanged = value; + OnPropertyChanged(); + } + } + + private bool _isDefaultLocation; + + /// + /// Indicates whether the current location is the default profiles folder location. + /// + public bool IsDefaultLocation + { + get => _isDefaultLocation; + set + { + if (value == _isDefaultLocation) + return; + + _isDefaultLocation = value; + OnPropertyChanged(); + } + } + private readonly ICollectionView _profileFiles; public ICollectionView ProfileFiles @@ -126,6 +171,7 @@ public SettingsProfilesViewModel() private void LoadSettings() { Location = ProfileManager.GetProfilesFolderLocation(); + IsDefaultLocation = string.Equals(Location, ProfileManager.GetDefaultProfilesFolderLocation(), StringComparison.OrdinalIgnoreCase); IsDailyBackupEnabled = SettingsManager.Current.Profiles_IsDailyBackupEnabled; MaximumNumberOfBackups = SettingsManager.Current.Profiles_MaximumNumberOfBackups; } @@ -141,6 +187,101 @@ private static void OpenLocationAction() Process.Start("explorer.exe", ProfileManager.GetProfilesFolderLocation()); } + /// + /// Gets the command that opens the location folder selection dialog. + /// + public ICommand BrowseLocationFolderCommand => new RelayCommand(p => BrowseLocationFolderAction()); + + /// + /// Opens a dialog that allows the user to select a folder location and updates the Location property with the + /// selected path if the user confirms the selection. + /// + private void BrowseLocationFolderAction() + { + using var dialog = new System.Windows.Forms.FolderBrowserDialog(); + + if (Directory.Exists(Location)) + dialog.SelectedPath = Location; + + var dialogResult = dialog.ShowDialog(); + + if (dialogResult == System.Windows.Forms.DialogResult.OK) + Location = dialog.SelectedPath; + } + + /// + /// Sets the location path based on the provided drag-and-drop input. + /// + /// The path to set as the location. + public void SetLocationPathFromDragDrop(string path) + { + Location = path; + } + + /// + /// Gets the command that initiates the action to change the location. + /// + public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false)); + + /// + /// Prompts the user to confirm and then changes the location of the profiles folder. + /// + /// A task that represents the asynchronous operation. + private async Task ChangeLocationAction() + { + var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow, + Strings.ChangeLocationQuestion, + string.Format(Strings.ChangeLocationProfilesMessage, ProfileManager.GetProfilesFolderLocation(), Location), + ChildWindowIcon.Question, + Strings.Change); + + if (!result) + return; + + // Save settings at the current location before changing it to prevent + // unintended saves to the new location (e.g., triggered by background timer or the app close & restart). + SettingsManager.Save(); + + // Set new location in SettingsInfo + SettingsManager.Current.Profiles_CustomProfilesFolderLocation = Location; + SettingsManager.Save(); + + // Restart the application + (Application.Current.MainWindow as MainWindow)?.RestartApplication(); + } + + /// + /// Gets the command that restores the default location. + /// + public ICommand RestoreDefaultLocationCommand => new RelayCommand(_ => RestoreDefaultLocationActionAsync().ConfigureAwait(false)); + + /// + /// Restores the profiles folder location to the default path after obtaining user confirmation. + /// + /// A task that represents the asynchronous operation. + private async Task RestoreDefaultLocationActionAsync() + { + var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow, + Strings.RestoreDefaultLocationQuestion, + string.Format(Strings.RestoreDefaultLocationProfilesMessage, ProfileManager.GetProfilesFolderLocation(), ProfileManager.GetDefaultProfilesFolderLocation()), + ChildWindowIcon.Question, + Strings.Restore); + + if (!result) + return; + + // Save settings at the current location before changing it to prevent + // unintended saves to the new location (e.g., triggered by background timer or the app close & restart). + SettingsManager.Save(); + + // Clear custom location to revert to default + SettingsManager.Current.Profiles_CustomProfilesFolderLocation = null; + SettingsManager.Save(); + + // Restart the application + (Application.Current.MainWindow as MainWindow)?.RestartApplication(); + } + public ICommand AddProfileFileCommand => new RelayCommand(async _ => await AddProfileFileAction().ConfigureAwait(false)); private async Task AddProfileFileAction() diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs index e14ee3639c..b726b0cab9 100644 --- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs @@ -50,7 +50,7 @@ public string Location /// /// Indicates whether the settings location is managed by a system-wide policy. /// - public bool IsLocationManagedByPolicy => !string.IsNullOrWhiteSpace(PolicyManager.Current?.SettingsFolderLocation); + public bool IsLocationManagedByPolicy => !string.IsNullOrWhiteSpace(PolicyManager.Current?.Settings_FolderLocation); /// /// Private field of property. @@ -265,7 +265,7 @@ private async Task ChangeLocationAction() SettingsManager.Save(); // Set new location - LocalSettingsManager.Current.SettingsFolderLocation = Location; + LocalSettingsManager.Current.Settings_FolderLocation = Location; LocalSettingsManager.Save(); // Restart the application @@ -301,7 +301,7 @@ private async Task RestoreDefaultLocationActionAsync() SettingsManager.Save(); // Clear custom location to revert to default - LocalSettingsManager.Current.SettingsFolderLocation = null; + LocalSettingsManager.Current.Settings_FolderLocation = null; LocalSettingsManager.Save(); // Restart the application diff --git a/Source/NETworkManager/Views/SettingsProfilesView.xaml b/Source/NETworkManager/Views/SettingsProfilesView.xaml index 0989c4a24d..3a19703688 100644 --- a/Source/NETworkManager/Views/SettingsProfilesView.xaml +++ b/Source/NETworkManager/Views/SettingsProfilesView.xaml @@ -10,36 +10,178 @@ xmlns:viewModels="clr-namespace:NETworkManager.ViewModels" xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization" xmlns:profiles="clr-namespace:NETworkManager.Profiles;assembly=NETworkManager.Profiles" + xmlns:validators="clr-namespace:NETworkManager.Validators;assembly=NETworkManager.Validators" + xmlns:settings="clr-namespace:NETworkManager.Settings;assembly=NETworkManager.Settings" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:SettingsProfilesViewModel}" Loaded="UserControl_Loaded"> + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Check for updates at startup](../settings/update.md#check-for-updates-at-startup) documentation for more information) - Settings folder location (see [Settings > Location](../settings/settings.md#location) documentation for more information) + - Profiles folder location (see [Profiles > Location](../settings/profiles.md#location) documentation for more information)
@@ -61,6 +62,10 @@ Release date: **xx.xx.2025** - Settings folder location can now be changed by the user in the settings (see [Settings > Location](../settings/settings.md#location) documentation for more information). Additionally, it can be configured via a system-wide policy, allowing administrators to enforce a specific location for all users. [#3324](https://github.com/BornToBeRoot/NETworkManager/pull/3324) +**Profiles** + +- Profiles folder location can now be changed by the user in the settings (see [Profiles > Location](../settings/profiles.md#location) documentation for more information). Additionally, it can be configured via a system-wide policy, allowing administrators to enforce a specific location for all users. + **Update** - The update check at startup can now be configured via a system-wide policy, allowing administrators to control whether the application checks for updates (see [Update > Check for updates at startup](../settings/update.md#check-for-updates-at-startup) documentation for more information). [#3313](https://github.com/BornToBeRoot/NETworkManager/pull/3313) diff --git a/Website/docs/settings/profiles.md b/Website/docs/settings/profiles.md index 186924aa4b..bee88adbda 100644 --- a/Website/docs/settings/profiles.md +++ b/Website/docs/settings/profiles.md @@ -17,6 +17,30 @@ Folder where the application profiles are stored. | Setup / Archiv | `%UserProfile%\Documents\NETworkManager\Profiles` | | Portable | `\Profiles` | +
+System-Wide Policy + +This setting can be controlled by administrators using a system-wide policy. See [System-Wide Policies](../system-wide-policies.md) for more information. + +**Policy Property:** `Profiles_FolderLocation` + +**Values:** + +- Absolute path (e.g., `C:\\Path\\To\\Profiles`) +- Path with environment variables (e.g., `%UserProfile%\\NETworkManager\\Profiles`) +- UNC path (e.g., `\\\\server\\share$\\NETworkManager\\Profiles`) +- Omit the property to allow the default location logic to apply (portable vs. non-portable) + +**Example:** + +```json +{ + "Profiles_FolderLocation": "%UserProfile%\\NETworkManager\\Profiles" +} +``` + +
+ :::note **Recommendation** diff --git a/Website/docs/settings/settings.md b/Website/docs/settings/settings.md index 4164bf37b8..8ed1f795de 100644 --- a/Website/docs/settings/settings.md +++ b/Website/docs/settings/settings.md @@ -22,7 +22,7 @@ Folder where the application settings are stored. This setting can be controlled by administrators using a system-wide policy. See [System-Wide Policies](../system-wide-policies.md) for more information. -**Policy Property:** `SettingsFolderLocation` +**Policy Property:** `Settings_FolderLocation` **Values:** @@ -35,7 +35,7 @@ This setting can be controlled by administrators using a system-wide policy. See ```json { - "SettingsFolderLocation": "%UserProfile%\\NETworkManager\\Settings" + "Settings_FolderLocation": "%UserProfile%\\NETworkManager\\Settings" } ``` diff --git a/Website/docs/system-wide-policies.md b/Website/docs/system-wide-policies.md index 1756d5755a..103b1a8302 100644 --- a/Website/docs/system-wide-policies.md +++ b/Website/docs/system-wide-policies.md @@ -37,7 +37,7 @@ The `config.json` file uses a simple JSON structure to define policy values. An ```json { "Update_CheckForUpdatesAtStartup": false, - "SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings" + "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings" } ``` From 28cf394e9c74383bc595a382c37bbd5cf71c8b7b Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 22 Feb 2026 00:59:24 +0100 Subject: [PATCH 03/15] Chore: Update strings --- .../Resources/Strings.Designer.cs | 62 +++++++++---------- .../Resources/Strings.resx | 20 +++--- .../ViewModels/SettingsProfilesViewModel.cs | 4 +- .../ViewModels/SettingsSettingsViewModel.cs | 4 +- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs index b431a282ac..f7699adf75 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs +++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs @@ -1465,51 +1465,51 @@ public static string ChangeLocationQuestion { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The location is changed and the application is restarted afterwards. - /// - ///You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten. ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Changelog ähnelt. /// - public static string ChangeLocationSettingsMessage { + public static string Changelog { get { - return ResourceManager.GetString("ChangeLocationSettingsMessage", resourceCulture); + return ResourceManager.GetString("Changelog", resourceCulture); } } /// - /// Sucht eine lokalisierte Zeichenfolge, die The location is changed and the application is restarted afterwards. - /// - ///You can copy your profile files from "{0}" to "{1}" to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Change master password ähnelt. /// - public static string ChangeLocationProfilesMessage { + public static string ChangeMasterPassword { get { - return ResourceManager.GetString("ChangeLocationProfilesMessage", resourceCulture); + return ResourceManager.GetString("ChangeMasterPassword", resourceCulture); } } /// - /// Sucht eine lokalisierte Zeichenfolge, die Changelog ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Change Master Password... ähnelt. /// - public static string Changelog { + public static string ChangeMasterPasswordDots { get { - return ResourceManager.GetString("Changelog", resourceCulture); + return ResourceManager.GetString("ChangeMasterPasswordDots", resourceCulture); } } /// - /// Sucht eine lokalisierte Zeichenfolge, die Change master password ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die The profiles location is changed and the application is restarted afterwards. + /// + ///You can copy your profile files from “{0}” to “{1}” to migrate your existing profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt. /// - public static string ChangeMasterPassword { + public static string ChangeProfilesLocationMessage { get { - return ResourceManager.GetString("ChangeMasterPassword", resourceCulture); + return ResourceManager.GetString("ChangeProfilesLocationMessage", resourceCulture); } } /// - /// Sucht eine lokalisierte Zeichenfolge, die Change Master Password... ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die The settings location is changed and the application is restarted afterwards. + /// + ///You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten. ähnelt. /// - public static string ChangeMasterPasswordDots { + public static string ChangeSettingsLocationMessage { get { - return ResourceManager.GetString("ChangeMasterPasswordDots", resourceCulture); + return ResourceManager.GetString("ChangeSettingsLocationMessage", resourceCulture); } } @@ -8903,33 +8903,33 @@ public static string RestoreDefaultLocationQuestion { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The default path is restored and the application is restarted afterwards. + /// Sucht eine lokalisierte Zeichenfolge, die The default profiles location is restored and the application is restarted afterwards. /// - ///You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten. ähnelt. + ///You can copy your profile files from “{0}” to “{1}” to migrate your existing profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt. /// - public static string RestoreDefaultLocationSettingsMessage { + public static string RestoreDefaultProfilesLocationMessage { get { - return ResourceManager.GetString("RestoreDefaultLocationSettingsMessage", resourceCulture); + return ResourceManager.GetString("RestoreDefaultProfilesLocationMessage", resourceCulture); } } /// - /// Sucht eine lokalisierte Zeichenfolge, die The default path is restored and the application is restarted afterwards. - /// - ///You can copy your profile files from "{0}" to "{1}" to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Restore defaults ähnelt. /// - public static string RestoreDefaultLocationProfilesMessage { + public static string RestoreDefaults { get { - return ResourceManager.GetString("RestoreDefaultLocationProfilesMessage", resourceCulture); + return ResourceManager.GetString("RestoreDefaults", resourceCulture); } } /// - /// Sucht eine lokalisierte Zeichenfolge, die Restore defaults ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die The default settings location is restored and the application is restarted afterwards. + /// + ///You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten. ähnelt. /// - public static string RestoreDefaults { + public static string RestoreDefaultSettingsLocationMessage { get { - return ResourceManager.GetString("RestoreDefaults", resourceCulture); + return ResourceManager.GetString("RestoreDefaultSettingsLocationMessage", resourceCulture); } } diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx index 550f519f38..7c2471b85e 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.resx +++ b/Source/NETworkManager.Localization/Resources/Strings.resx @@ -3972,30 +3972,30 @@ If you click Cancel, the profile file will remain unencrypted. Restore default location? - - The default path is restored and the application is restarted afterwards. + + The default settings location is restored and the application is restarted afterwards. You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten. Change location? - - The location is changed and the application is restarted afterwards. + + The settings location is changed and the application is restarted afterwards. You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten. Enter a valid folder path! - - The location is changed and the application is restarted afterwards. + + The profiles location is changed and the application is restarted afterwards. -You can copy your profile files from “{0}” to “{1}” to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. +You can copy your profile files from “{0}” to “{1}” to migrate your existing profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. - - The default path is restored and the application is restarted afterwards. + + The default profiles location is restored and the application is restarted afterwards. -You can copy your profile files from “{0}” to “{1}” to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. +You can copy your profile files from “{0}” to “{1}” to migrate your existing profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs index c9efb56184..3a02eff54c 100644 --- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs @@ -231,7 +231,7 @@ private async Task ChangeLocationAction() { var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow, Strings.ChangeLocationQuestion, - string.Format(Strings.ChangeLocationProfilesMessage, ProfileManager.GetProfilesFolderLocation(), Location), + string.Format(Strings.ChangeProfilesLocationMessage, ProfileManager.GetProfilesFolderLocation(), Location), ChildWindowIcon.Question, Strings.Change); @@ -263,7 +263,7 @@ private async Task RestoreDefaultLocationActionAsync() { var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow, Strings.RestoreDefaultLocationQuestion, - string.Format(Strings.RestoreDefaultLocationProfilesMessage, ProfileManager.GetProfilesFolderLocation(), ProfileManager.GetDefaultProfilesFolderLocation()), + string.Format(Strings.RestoreDefaultProfilesLocationMessage, ProfileManager.GetProfilesFolderLocation(), ProfileManager.GetDefaultProfilesFolderLocation()), ChildWindowIcon.Question, Strings.Restore); diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs index b726b0cab9..ecdba124b7 100644 --- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs @@ -253,7 +253,7 @@ private async Task ChangeLocationAction() { var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow, Strings.ChangeLocationQuestion, - string.Format(Strings.ChangeLocationSettingsMessage, SettingsManager.GetSettingsFolderLocation(), Location), + string.Format(Strings.ChangeSettingsLocationMessage, SettingsManager.GetSettingsFolderLocation(), Location), ChildWindowIcon.Question, Strings.Change); @@ -289,7 +289,7 @@ private async Task RestoreDefaultLocationActionAsync() { var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow, Strings.RestoreDefaultLocationQuestion, - string.Format(Strings.RestoreDefaultLocationSettingsMessage, SettingsManager.GetSettingsFolderLocation(), SettingsManager.GetDefaultSettingsFolderLocation()), + string.Format(Strings.RestoreDefaultSettingsLocationMessage, SettingsManager.GetSettingsFolderLocation(), SettingsManager.GetDefaultSettingsFolderLocation()), ChildWindowIcon.Question, Strings.Restore); From cdbb41f890d9266625d8e175ecf8e70bc58cc8b7 Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:02:49 +0100 Subject: [PATCH 04/15] Docs: ... --- Website/docs/system-wide-policies.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Website/docs/system-wide-policies.md b/Website/docs/system-wide-policies.md index 103b1a8302..a53dce282d 100644 --- a/Website/docs/system-wide-policies.md +++ b/Website/docs/system-wide-policies.md @@ -36,8 +36,9 @@ The `config.json` file uses a simple JSON structure to define policy values. An ```json { + "Profiles_FolderLocation": "\\\\Server\\Shared\\NETworkManager\\Profiles", + "Settings_FolderLocation": "%UserProfile%\\NETworkManager\\Settings", "Update_CheckForUpdatesAtStartup": false, - "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings" } ``` From 29ea56f2a443f3dcb3dba1771f00747a66156c86 Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:04:39 +0100 Subject: [PATCH 05/15] Docs: #3340 --- Website/docs/changelog/next-release.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md index d44e95fbba..410eb9c036 100644 --- a/Website/docs/changelog/next-release.md +++ b/Website/docs/changelog/next-release.md @@ -58,13 +58,13 @@ Release date: **xx.xx.2025** ::: -**Settings** +**Profiles** -- Settings folder location can now be changed by the user in the settings (see [Settings > Location](../settings/settings.md#location) documentation for more information). Additionally, it can be configured via a system-wide policy, allowing administrators to enforce a specific location for all users. [#3324](https://github.com/BornToBeRoot/NETworkManager/pull/3324) +- Profiles folder location can now be changed by the user in the settings (see [Profiles > Location](../settings/profiles.md#location) documentation for more information). Additionally, it can be configured via a system-wide policy, allowing administrators to enforce a specific location for all users. [#3340](https://github.com/BornToBeRoot/NETworkManager/pull/3340) -**Profiles** +**Settings** -- Profiles folder location can now be changed by the user in the settings (see [Profiles > Location](../settings/profiles.md#location) documentation for more information). Additionally, it can be configured via a system-wide policy, allowing administrators to enforce a specific location for all users. +- Settings folder location can now be changed by the user in the settings (see [Settings > Location](../settings/settings.md#location) documentation for more information). Additionally, it can be configured via a system-wide policy, allowing administrators to enforce a specific location for all users. [#3324](https://github.com/BornToBeRoot/NETworkManager/pull/3324) **Update** From 37ef327051586e682b6a59080f8e5b34260edf44 Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:06:48 +0100 Subject: [PATCH 06/15] Update config.json.example --- Source/NETworkManager.Settings/config.json.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/NETworkManager.Settings/config.json.example b/Source/NETworkManager.Settings/config.json.example index 3747efaeaa..008da6fc63 100644 --- a/Source/NETworkManager.Settings/config.json.example +++ b/Source/NETworkManager.Settings/config.json.example @@ -1,5 +1,5 @@ { "Update_CheckForUpdatesAtStartup": false, - "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings", - "Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles" + "Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles", + "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings" } \ No newline at end of file From 484edb5a3131e7f8e6c2185235817f729866599f Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:15:55 +0100 Subject: [PATCH 07/15] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Source/NETworkManager/Views/SettingsProfilesView.xaml | 2 +- Source/NETworkManager/Views/SettingsProfilesView.xaml.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/NETworkManager/Views/SettingsProfilesView.xaml b/Source/NETworkManager/Views/SettingsProfilesView.xaml index 3a19703688..52b54f7010 100644 --- a/Source/NETworkManager/Views/SettingsProfilesView.xaml +++ b/Source/NETworkManager/Views/SettingsProfilesView.xaml @@ -17,7 +17,7 @@ - + 0 }) _viewModel.SetLocationPathFromDragDrop(files[0]); } private void TextBoxLocation_PreviewDragOver(object sender, DragEventArgs e) { - e.Effects = DragDropEffects.Copy; + e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) + ? DragDropEffects.Copy + : DragDropEffects.None; e.Handled = true; } } \ No newline at end of file From 2a53696218b99584f294180badfad2c9e1d47527 Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:16:07 +0100 Subject: [PATCH 08/15] Chore: Minor improvements --- Source/NETworkManager.Settings/LocalSettingsInfo.cs | 2 +- Source/NETworkManager.Settings/PolicyInfo.cs | 6 +++--- Source/NETworkManager.Settings/PolicyManager.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/NETworkManager.Settings/LocalSettingsInfo.cs b/Source/NETworkManager.Settings/LocalSettingsInfo.cs index b3388d396e..79c02a1937 100644 --- a/Source/NETworkManager.Settings/LocalSettingsInfo.cs +++ b/Source/NETworkManager.Settings/LocalSettingsInfo.cs @@ -34,7 +34,7 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null) [JsonIgnore] public bool SettingsChanged { get; set; } /// - /// Private field for the property." + /// Private field for the property. /// private string _settings_FolderLocation; diff --git a/Source/NETworkManager.Settings/PolicyInfo.cs b/Source/NETworkManager.Settings/PolicyInfo.cs index 55d6767084..811360e765 100644 --- a/Source/NETworkManager.Settings/PolicyInfo.cs +++ b/Source/NETworkManager.Settings/PolicyInfo.cs @@ -11,9 +11,9 @@ public class PolicyInfo [JsonPropertyName("Update_CheckForUpdatesAtStartup")] public bool? Update_CheckForUpdatesAtStartup { get; set; } - [JsonPropertyName("Settings_FolderLocation")] - public string? Settings_FolderLocation { get; set; } - [JsonPropertyName("Profiles_FolderLocation")] public string? Profiles_FolderLocation { get; set; } + + [JsonPropertyName("Settings_FolderLocation")] + public string? Settings_FolderLocation { get; set; } } diff --git a/Source/NETworkManager.Settings/PolicyManager.cs b/Source/NETworkManager.Settings/PolicyManager.cs index 26063ac16b..e7708db5fa 100644 --- a/Source/NETworkManager.Settings/PolicyManager.cs +++ b/Source/NETworkManager.Settings/PolicyManager.cs @@ -84,8 +84,8 @@ public static void Load() // Log enabled settings Log.Info($"System-wide policy - Update_CheckForUpdatesAtStartup: {Current.Update_CheckForUpdatesAtStartup?.ToString() ?? "Not set"}"); - Log.Info($"System-wide policy - Settings_FolderLocation: {Current.Settings_FolderLocation ?? "Not set"}"); Log.Info($"System-wide policy - Profiles_FolderLocation: {Current.Profiles_FolderLocation ?? "Not set"}"); + Log.Info($"System-wide policy - Settings_FolderLocation: {Current.Settings_FolderLocation ?? "Not set"}"); } } catch (Exception ex) From 620315e32fc83c4adad764ecf6569a5192b6f310 Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:17:29 +0100 Subject: [PATCH 09/15] Update SettingsSettingsView.xaml.cs --- Source/NETworkManager/Views/SettingsSettingsView.xaml.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/NETworkManager/Views/SettingsSettingsView.xaml.cs b/Source/NETworkManager/Views/SettingsSettingsView.xaml.cs index 8c840314d5..54c6a604ff 100644 --- a/Source/NETworkManager/Views/SettingsSettingsView.xaml.cs +++ b/Source/NETworkManager/Views/SettingsSettingsView.xaml.cs @@ -31,13 +31,15 @@ private void TextBoxLocation_Drop(object sender, DragEventArgs e) var files = (string[])e.Data.GetData(DataFormats.FileDrop); - if (files != null) + if (files is { Length: > 0 }) _viewModel.SetLocationPathFromDragDrop(files[0]); } private void TextBoxLocation_PreviewDragOver(object sender, DragEventArgs e) { - e.Effects = DragDropEffects.Copy; + e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) + ? DragDropEffects.Copy + : DragDropEffects.None; e.Handled = true; } -} \ No newline at end of file +} From 37209464346550f95c9f6142e63c77d30e6765f4 Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:59:01 +0100 Subject: [PATCH 10/15] Chore: Minor refactoring --- .../NETworkManager.Profiles/ProfileManager.cs | 76 +++---------------- .../NETworkManager.Settings/SettingsInfo.cs | 36 ++++----- .../SettingsManager.cs | 72 ++---------------- .../DirectoryHelper.cs | 70 +++++++++++++++++ .../ViewModels/SettingsProfilesViewModel.cs | 4 +- 5 files changed, 106 insertions(+), 152 deletions(-) diff --git a/Source/NETworkManager.Profiles/ProfileManager.cs b/Source/NETworkManager.Profiles/ProfileManager.cs index 6978c51d45..c32ae88977 100644 --- a/Source/NETworkManager.Profiles/ProfileManager.cs +++ b/Source/NETworkManager.Profiles/ProfileManager.cs @@ -197,7 +197,10 @@ private static void ProfilesUpdated(bool profilesChanged = true) /// /// Method to get the path of the profiles folder. - /// Priority: 1. Policy override, 2. Custom user path (from SettingsInfo), 3. Portable/default. + /// Priority: + /// 1. Policy override (for IT administrators) + /// 2. Custom user-configured path (not available in portable mode) + /// 3. Portable (same directory as the application) or default location (Documents folder) /// /// Path to the profiles folder. public static string GetProfilesFolderLocation() @@ -205,9 +208,10 @@ public static string GetProfilesFolderLocation() // 1. Policy override takes precedence (for IT administrators) if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.Profiles_FolderLocation)) { - var validatedPath = ValidateProfilesFolderPath( + var validatedPath = DirectoryHelper.ValidateFolderPath( PolicyManager.Current.Profiles_FolderLocation, "Policy-provided", + nameof(PolicyInfo.Profiles_FolderLocation), "next priority"); if (validatedPath != null) @@ -216,11 +220,12 @@ public static string GetProfilesFolderLocation() // 2. Custom user-configured path (not available in portable mode) if (!ConfigurationManager.Current.IsPortable && - !string.IsNullOrWhiteSpace(SettingsManager.Current?.Profiles_CustomProfilesFolderLocation)) + !string.IsNullOrWhiteSpace(SettingsManager.Current?.Profiles_FolderLocation)) { - var validatedPath = ValidateProfilesFolderPath( - SettingsManager.Current.Profiles_CustomProfilesFolderLocation, + var validatedPath = DirectoryHelper.ValidateFolderPath( + SettingsManager.Current.Profiles_FolderLocation, "Custom", + nameof(SettingsInfo.Profiles_FolderLocation), "default location"); if (validatedPath != null) @@ -253,67 +258,6 @@ public static string GetPortableProfilesFolderLocation() return Path.Combine(AssemblyManager.Current.Location, ProfilesFolderName); } - /// - /// Validates a profiles folder path for correctness and accessibility. - /// - /// The path to validate. - /// Description of the path source for logging (e.g., "Policy-provided", "Custom"). - /// Message describing what happens on validation failure (e.g., "next priority", "default location"). - /// The validated full path if valid; otherwise, null. - private static string ValidateProfilesFolderPath(string path, string pathSource, string fallbackMessage) - { - // Expand environment variables first (e.g. %userprofile%\profiles -> C:\Users\...\profiles) - path = Environment.ExpandEnvironmentVariables(path); - - // Validate that the path is rooted (absolute) - if (!Path.IsPathRooted(path)) - { - Log.Error($"{pathSource} Profiles_FolderLocation is not an absolute path: {path}. Falling back to {fallbackMessage}."); - return null; - } - - // Validate that the path doesn't contain invalid characters - try - { - // This will throw ArgumentException, NotSupportedException, SecurityException, PathTooLongException, or IOException if the path is invalid - var fullPath = Path.GetFullPath(path); - - // Check if the path is a directory (not a file) - if (File.Exists(fullPath)) - { - Log.Error($"{pathSource} Profiles_FolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}."); - return null; - } - - return Path.TrimEndingDirectorySeparator(fullPath); - } - catch (ArgumentException ex) - { - Log.Error($"{pathSource} Profiles_FolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex); - return null; - } - catch (NotSupportedException ex) - { - Log.Error($"{pathSource} Profiles_FolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex); - return null; - } - catch (SecurityException ex) - { - Log.Error($"Insufficient permissions to access {pathSource} Profiles_FolderLocation: {path}. Falling back to {fallbackMessage}.", ex); - return null; - } - catch (PathTooLongException ex) - { - Log.Error($"{pathSource} Profiles_FolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex); - return null; - } - catch (IOException ex) - { - Log.Error($"{pathSource} Profiles_FolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex); - return null; - } - } - /// /// Method to get the path of the profiles backup folder. /// diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs index 0bbf9d31f1..01f641b0b4 100644 --- a/Source/NETworkManager.Settings/SettingsInfo.cs +++ b/Source/NETworkManager.Settings/SettingsInfo.cs @@ -576,7 +576,22 @@ public bool Experimental_EnableExperimentalFeatures } } - // Profiles + // Profiles + private string _profiles_FolderLocation; + + public string Profiles_FolderLocation + { + get => _profiles_FolderLocation; + set + { + if (value == _profiles_FolderLocation) + return; + + _profiles_FolderLocation = value; + OnPropertyChanged(); + } + } + private string _profiles_LastSelected; public string Profiles_LastSelected @@ -622,25 +637,6 @@ public int Profiles_MaximumNumberOfBackups } } - private string _profiles_CustomProfilesFolderLocation; - - /// - /// Custom profiles folder location set by the user. - /// When set, overrides the default profiles folder location. - /// - public string Profiles_CustomProfilesFolderLocation - { - get => _profiles_CustomProfilesFolderLocation; - set - { - if (value == _profiles_CustomProfilesFolderLocation) - return; - - _profiles_CustomProfilesFolderLocation = value; - OnPropertyChanged(); - } - } - // Settings private bool _settings_IsDailyBackupEnabled = GlobalStaticConfiguration.Settings_IsDailyBackupEnabled; diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs index 6852016be9..286a928728 100644 --- a/Source/NETworkManager.Settings/SettingsManager.cs +++ b/Source/NETworkManager.Settings/SettingsManager.cs @@ -5,7 +5,6 @@ using System; using System.IO; using System.Linq; -using System.Security; using System.Text.Json; using System.Text.Json.Serialization; using System.Xml.Serialization; @@ -74,6 +73,10 @@ public static class SettingsManager /// /// Method to get the path of the settings folder. + /// Priority: + /// 1. Policy override (for IT administrators) + /// 2. Custom user-configured path (not available in portable mode) + /// 3. Portable (same directory as the application) or default location (Documents folder) /// /// Path to the settings folder. public static string GetSettingsFolderLocation() @@ -81,9 +84,10 @@ public static string GetSettingsFolderLocation() // 1. Policy override takes precedence (for IT administrators) if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.Settings_FolderLocation)) { - var validatedPath = ValidateSettingsFolderPath( + var validatedPath = DirectoryHelper.ValidateFolderPath( PolicyManager.Current.Settings_FolderLocation, "Policy-provided", + nameof(PolicyInfo.Settings_FolderLocation), "next priority"); if (validatedPath != null) @@ -94,9 +98,10 @@ public static string GetSettingsFolderLocation() if (!ConfigurationManager.Current.IsPortable && !string.IsNullOrWhiteSpace(LocalSettingsManager.Current?.Settings_FolderLocation)) { - var validatedPath = ValidateSettingsFolderPath( + var validatedPath = DirectoryHelper.ValidateFolderPath( LocalSettingsManager.Current.Settings_FolderLocation, "Custom", + nameof(LocalSettingsInfo.Settings_FolderLocation), "default location"); if (validatedPath != null) @@ -129,67 +134,6 @@ public static string GetPortableSettingsFolderLocation() return Path.Combine(AssemblyManager.Current.Location, SettingsFolderName); } - /// - /// Validates a settings folder path for correctness and accessibility. - /// - /// The path to validate. - /// Description of the path source for logging (e.g., "Policy-provided", "Custom"). - /// Message describing what happens on validation failure (e.g., "next priority", "default location"). - /// The validated full path if valid; otherwise, null. - private static string ValidateSettingsFolderPath(string path, string pathSource, string fallbackMessage) - { - // Expand environment variables first (e.g. %userprofile%\settings -> C:\Users\...\settings) - path = Environment.ExpandEnvironmentVariables(path); - - // Validate that the path is rooted (absolute) - if (!Path.IsPathRooted(path)) - { - Log.Error($"{pathSource} Settings_FolderLocation is not an absolute path: {path}. Falling back to {fallbackMessage}."); - return null; - } - - // Validate that the path doesn't contain invalid characters - try - { - // This will throw ArgumentException, NotSupportedException, SecurityException, PathTooLongException, or IOException if the path is invalid - var fullPath = Path.GetFullPath(path); - - // Check if the path is a directory (not a file) - if (File.Exists(fullPath)) - { - Log.Error($"{pathSource} Settings_FolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}."); - return null; - } - - return Path.TrimEndingDirectorySeparator(fullPath); - } - catch (ArgumentException ex) - { - Log.Error($"{pathSource} Settings_FolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex); - return null; - } - catch (NotSupportedException ex) - { - Log.Error($"{pathSource} Settings_FolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex); - return null; - } - catch (SecurityException ex) - { - Log.Error($"Insufficient permissions to access {pathSource} Settings_FolderLocation: {path}. Falling back to {fallbackMessage}.", ex); - return null; - } - catch (PathTooLongException ex) - { - Log.Error($"{pathSource} Settings_FolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex); - return null; - } - catch (IOException ex) - { - Log.Error($"{pathSource} Settings_FolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex); - return null; - } - } - /// /// Method to get the path of the settings backup folder. /// diff --git a/Source/NETworkManager.Utilities/DirectoryHelper.cs b/Source/NETworkManager.Utilities/DirectoryHelper.cs index 25353b8cc1..c7aeef7efb 100644 --- a/Source/NETworkManager.Utilities/DirectoryHelper.cs +++ b/Source/NETworkManager.Utilities/DirectoryHelper.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Security; +using log4net; namespace NETworkManager.Utilities; @@ -8,6 +10,11 @@ namespace NETworkManager.Utilities; ///
public static class DirectoryHelper { + /// + /// Logger for logging. + /// + private static readonly ILog Log = LogManager.GetLogger(typeof(DirectoryHelper)); + /// /// Create a directory with subdirectories and resolve environment variables. /// @@ -16,4 +23,67 @@ public static void CreateWithEnvironmentVariables(string path) { Directory.CreateDirectory(Environment.ExpandEnvironmentVariables(path)); } + + /// + /// Validates a folder path for correctness and accessibility. + /// Expands environment variables, checks that the path is absolute, + /// validates characters, and ensures it does not point to a file. + /// + /// The path to validate. + /// Description of the path source for logging (e.g., "Policy-provided", "Custom"). + /// Name of the property being validated for logging (e.g., "Settings_FolderLocation"). + /// Message describing what happens on validation failure (e.g., "next priority", "default location"). + /// The validated full path if valid; otherwise, null. + public static string ValidateFolderPath(string path, string pathSource, string propertyName, string fallbackMessage) + { + // Expand environment variables first (e.g. %userprofile%\settings -> C:\Users\...\settings) + path = Environment.ExpandEnvironmentVariables(path); + + // Validate that the path is rooted (absolute) + if (!Path.IsPathRooted(path)) + { + Log.Error($"{pathSource} {propertyName} is not an absolute path: {path}. Falling back to {fallbackMessage}."); + return null; + } + + // Validate that the path doesn't contain invalid characters + try + { + var fullPath = Path.GetFullPath(path); + + // Check if the path is a directory (not a file) + if (File.Exists(fullPath)) + { + Log.Error($"{pathSource} {propertyName} is a file, not a directory: {path}. Falling back to {fallbackMessage}."); + return null; + } + + return Path.TrimEndingDirectorySeparator(fullPath); + } + catch (ArgumentException ex) + { + Log.Error($"{pathSource} {propertyName} contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex); + return null; + } + catch (NotSupportedException ex) + { + Log.Error($"{pathSource} {propertyName} format is not supported: {path}. Falling back to {fallbackMessage}.", ex); + return null; + } + catch (SecurityException ex) + { + Log.Error($"Insufficient permissions to access {pathSource} {propertyName}: {path}. Falling back to {fallbackMessage}.", ex); + return null; + } + catch (PathTooLongException ex) + { + Log.Error($"{pathSource} {propertyName} path is too long: {path}. Falling back to {fallbackMessage}.", ex); + return null; + } + catch (IOException ex) + { + Log.Error($"{pathSource} {propertyName} caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex); + return null; + } + } } \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs index 3a02eff54c..fa9c3b9476 100644 --- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs @@ -243,7 +243,7 @@ private async Task ChangeLocationAction() SettingsManager.Save(); // Set new location in SettingsInfo - SettingsManager.Current.Profiles_CustomProfilesFolderLocation = Location; + SettingsManager.Current.Profiles_FolderLocation = Location; SettingsManager.Save(); // Restart the application @@ -275,7 +275,7 @@ private async Task RestoreDefaultLocationActionAsync() SettingsManager.Save(); // Clear custom location to revert to default - SettingsManager.Current.Profiles_CustomProfilesFolderLocation = null; + SettingsManager.Current.Profiles_FolderLocation = null; SettingsManager.Save(); // Restart the application From 09af5ced39c210bc4c0d3c584fb682d24de3036d Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:25:35 +0100 Subject: [PATCH 11/15] Chore: Updates --- .../ViewModels/SettingsProfilesViewModel.cs | 83 ++++++++++++----- .../ViewModels/SettingsSettingsViewModel.cs | 91 ++++++++----------- 2 files changed, 94 insertions(+), 80 deletions(-) diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs index fa9c3b9476..a2e660327e 100644 --- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs @@ -48,6 +48,9 @@ public string Location ///
public bool IsLocationManagedByPolicy => !string.IsNullOrWhiteSpace(PolicyManager.Current?.Profiles_FolderLocation); + /// + /// Private field of property. + /// private bool _isLocationChanged; /// @@ -66,6 +69,9 @@ public bool IsLocationChanged } } + /// + /// Private field of property. + /// private bool _isDefaultLocation; /// @@ -84,8 +90,14 @@ public bool IsDefaultLocation } } + /// + /// Private field of property. + /// private readonly ICollectionView _profileFiles; + /// + /// Gets the collection view of profile files. + /// public ICollectionView ProfileFiles { get => _profileFiles; @@ -99,8 +111,15 @@ private init } } + /// + /// Private field of property. + /// + private ProfileFileInfo _selectedProfileFile; + /// + /// Gets or sets the currently selected profile file information. + /// public ProfileFileInfo SelectedProfileFile { get => _selectedProfileFile; @@ -114,8 +133,15 @@ public ProfileFileInfo SelectedProfileFile } } + /// + /// Private field of property. + /// private bool _isDailyBackupEnabled; + + /// + /// Gets or sets a value indicating whether daily backups are enabled. + /// public bool IsDailyBackupEnabled { get => _isDailyBackupEnabled; @@ -132,8 +158,14 @@ public bool IsDailyBackupEnabled } } + /// + /// Private field of property. + /// private int _maximumNumberOfBackups; + /// + /// Gets or sets the maximum number of backups to keep. + /// public int MaximumNumberOfBackups { get => _maximumNumberOfBackups; @@ -153,6 +185,9 @@ public int MaximumNumberOfBackups #region Constructor, LoadSettings + /// + /// Initializes a new instance of the class and loads the current profile files. + /// public SettingsProfilesViewModel() { _isLoading = true; @@ -168,6 +203,9 @@ public SettingsProfilesViewModel() _isLoading = false; } + /// + /// Load view specific settings. + /// private void LoadSettings() { Location = ProfileManager.GetProfilesFolderLocation(); @@ -179,14 +217,6 @@ private void LoadSettings() #endregion #region ICommands & Actions - - public ICommand OpenLocationCommand => new RelayCommand(_ => OpenLocationAction()); - - private static void OpenLocationAction() - { - Process.Start("explorer.exe", ProfileManager.GetProfilesFolderLocation()); - } - /// /// Gets the command that opens the location folder selection dialog. /// @@ -209,14 +239,16 @@ private void BrowseLocationFolderAction() Location = dialog.SelectedPath; } + /// - /// Sets the location path based on the provided drag-and-drop input. - /// - /// The path to set as the location. - public void SetLocationPathFromDragDrop(string path) + /// Gets the command that initiates the action to change the location. + /// + public ICommand OpenLocationCommand => new RelayCommand(_ => OpenLocationAction()); + + private static void OpenLocationAction() { - Location = path; - } + Process.Start("explorer.exe", ProfileManager.GetProfilesFolderLocation()); + } /// /// Gets the command that initiates the action to change the location. @@ -238,13 +270,8 @@ private async Task ChangeLocationAction() if (!result) return; - // Save settings at the current location before changing it to prevent - // unintended saves to the new location (e.g., triggered by background timer or the app close & restart). - SettingsManager.Save(); - // Set new location in SettingsInfo SettingsManager.Current.Profiles_FolderLocation = Location; - SettingsManager.Save(); // Restart the application (Application.Current.MainWindow as MainWindow)?.RestartApplication(); @@ -270,14 +297,9 @@ private async Task RestoreDefaultLocationActionAsync() if (!result) return; - // Save settings at the current location before changing it to prevent - // unintended saves to the new location (e.g., triggered by background timer or the app close & restart). - SettingsManager.Save(); - // Clear custom location to revert to default SettingsManager.Current.Profiles_FolderLocation = null; - SettingsManager.Save(); - + // Restart the application (Application.Current.MainWindow as MainWindow)?.RestartApplication(); } @@ -551,4 +573,15 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow, } #endregion + + #region Methods + /// + /// Sets the location path based on the provided drag-and-drop input. + /// + /// The path to set as the location. + public void SetLocationPathFromDragDrop(string path) + { + Location = path; + } + #endregion } diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs index ecdba124b7..6212c228ba 100644 --- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs @@ -159,8 +159,8 @@ public SettingsSettingsViewModel() } /// - /// Loads the application settings from the current settings folder location. - /// + /// Load view specific settings. + /// private void LoadSettings() { Location = SettingsManager.GetSettingsFolderLocation(); @@ -172,36 +172,6 @@ private void LoadSettings() #endregion #region ICommands & Actions - - /// - /// Gets the command that opens a location when executed. - /// - public ICommand OpenLocationCommand => new RelayCommand(_ => OpenLocationAction()); - - /// - /// Opens the settings folder location in Windows Explorer. - /// - private static void OpenLocationAction() - { - Process.Start("explorer.exe", SettingsManager.GetSettingsFolderLocation()); - } - - /// - /// Gets the command that resets the application settings to their default values. - /// - public ICommand ResetSettingsCommand => new RelayCommand(_ => ResetSettingsAction()); - - /// - /// Resets the application settings to their default values. - /// - private void ResetSettingsAction() - { - ResetSettings().ConfigureAwait(false); - } - - #endregion - - #region Methods /// /// Gets the command that opens the location folder selection dialog. /// @@ -210,10 +180,7 @@ private void ResetSettingsAction() /// /// Opens a dialog that allows the user to select a folder location and updates the Location property with the /// selected path if the user confirms the selection. - /// - /// If the Location property is set to a valid directory path, it is pre-selected in the dialog. - /// This method does not return a value and is intended for use in a user interface context where folder selection - /// is required. + /// private void BrowseLocationFolderAction() { using var dialog = new System.Windows.Forms.FolderBrowserDialog(); @@ -228,12 +195,16 @@ private void BrowseLocationFolderAction() } /// - /// Sets the location path based on the provided drag-and-drop input. - /// - /// The path to set as the location. This value cannot be null or empty. - public void SetLocationPathFromDragDrop(string path) + /// Gets the command that opens a location when executed. + /// + public ICommand OpenLocationCommand => new RelayCommand(_ => OpenLocationAction()); + + /// + /// Opens the settings folder location in Windows Explorer. + /// + private static void OpenLocationAction() { - Location = path; + Process.Start("explorer.exe", SettingsManager.GetSettingsFolderLocation()); } /// @@ -242,12 +213,8 @@ public void SetLocationPathFromDragDrop(string path) public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false)); /// - /// Prompts the user to confirm and then changes the location of the application's settings folder. + /// Prompts the user to confirm and then changes the location of the profiles folder. /// - /// This method displays a confirmation dialog to the user before changing the settings folder - /// location. If the user confirms, it saves the current settings, updates the settings folder location, and - /// restarts the application to apply the changes. No action is taken if the user cancels the confirmation - /// dialog. /// A task that represents the asynchronous operation. private async Task ChangeLocationAction() { @@ -309,19 +276,21 @@ private async Task RestoreDefaultLocationActionAsync() } /// - /// Resets the application settings to their default values and restarts the application after user confirmation. + /// Gets the command that resets the application settings to their default values. + /// + public ICommand ResetSettingsCommand => new RelayCommand(_ => ResetSettingsAction().ConfigureAwait(false)); + + /// + /// Resets the application settings to their default values. /// - /// Displays a confirmation dialog to the user before proceeding. If the user confirms, the - /// settings are reinitialized to their defaults and the application is restarted. No action is taken if the user - /// cancels the confirmation dialog. /// A task that represents the asynchronous operation. - private async Task ResetSettings() + private async Task ResetSettingsAction() { var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow, - Strings.ResetSettingsQuestion, - Strings.SettingsAreResetAndApplicationWillBeRestartedMessage, - ChildWindowIcon.Question, - Strings.Reset); + Strings.ResetSettingsQuestion, + Strings.SettingsAreResetAndApplicationWillBeRestartedMessage, + ChildWindowIcon.Question, + Strings.Reset); if (!result) return; @@ -332,5 +301,17 @@ private async Task ResetSettings() // Restart the application (Application.Current.MainWindow as MainWindow)?.RestartApplication(); } + + #endregion + + #region Methods + /// + /// Sets the location path based on the provided drag-and-drop input. + /// + /// The path to set as the location. + public void SetLocationPathFromDragDrop(string path) + { + Location = path; + } #endregion } From 09c11864d77fe9bf31fbe44665c2c09d66f5f52e Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:50:14 +0100 Subject: [PATCH 12/15] Feature: Improve view --- .../config.json.example | 4 +- .../Views/SettingsProfilesView.xaml | 58 +++++++++--------- .../Views/SettingsSettingsView.xaml | 60 ++++++++++--------- 3 files changed, 66 insertions(+), 56 deletions(-) diff --git a/Source/NETworkManager.Settings/config.json.example b/Source/NETworkManager.Settings/config.json.example index 008da6fc63..2bf8307cfb 100644 --- a/Source/NETworkManager.Settings/config.json.example +++ b/Source/NETworkManager.Settings/config.json.example @@ -1,5 +1,5 @@ { "Update_CheckForUpdatesAtStartup": false, - "Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles", - "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings" + "Profiles_FolderLocation": "\\\\Server\\Shared\\NETworkManager\\Profiles", + "Settings_FolderLocation": "%UserProfile%\\NETworkManager\\Settings" } \ No newline at end of file diff --git a/Source/NETworkManager/Views/SettingsProfilesView.xaml b/Source/NETworkManager/Views/SettingsProfilesView.xaml index 52b54f7010..efa4d9c5d4 100644 --- a/Source/NETworkManager/Views/SettingsProfilesView.xaml +++ b/Source/NETworkManager/Views/SettingsProfilesView.xaml @@ -69,20 +69,12 @@ - - - @@ -156,10 +156,14 @@ - - + + - + - - + +