Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
702f681
Initial plan
Copilot Feb 1, 2026
a8853bc
Add system-wide config feature to disable update checks
Copilot Feb 1, 2026
83dc909
Add documentation for system-wide config feature
Copilot Feb 1, 2026
7dc158c
Fix documentation formatting in config README
Copilot Feb 1, 2026
36e595c
Rename ConfigManager to PolicyManager to avoid naming conflict
Copilot Feb 1, 2026
506e6bf
Add config.json.example to installation and UI for policy-managed set…
Copilot Feb 7, 2026
1b12a86
Add localization string for policy-managed setting message
Copilot Feb 7, 2026
9ed5c23
Improve accessibility with icon and add documentation to config example
Copilot Feb 7, 2026
509ede8
Chore: Move config file to settings namespace
BornToBeRoot Feb 8, 2026
247c4c5
Rename policy property to Update_CheckForUpdatesAtStartup and allow e…
Copilot Feb 8, 2026
5180ca0
Chore: Minor color adjustments
BornToBeRoot Feb 8, 2026
388c828
Merge branch 'main' into copilot/disable-update-check-all-users
BornToBeRoot Feb 8, 2026
84434ef
Use direct pattern for policy check and remove helper property
Copilot Feb 8, 2026
a83ee1d
Add Docusaurus documentation for system-wide policies and remove old …
Copilot Feb 8, 2026
65134b0
Fix changelog PR number placeholder
Copilot Feb 8, 2026
dbe56fe
Update system-wide-policies.md
BornToBeRoot Feb 8, 2026
3100ee9
Move policy property details to settings/update.md to avoid duplication
Copilot Feb 8, 2026
c5840c6
Feature: System wide policy
BornToBeRoot Feb 8, 2026
f02c7be
Fix Docusaurus build error with react-image-gallery CSS import
Copilot Feb 8, 2026
bafdcf3
Update browserslist database to latest version
Copilot Feb 8, 2026
6bd67c0
Add null-safety checks for PolicyManager deserialization
Copilot Feb 8, 2026
c08bb42
Fix react-image-gallery CSS import path for v2.0+
Copilot Feb 8, 2026
02b2ee7
Fix React version mismatch - update react-dom to 19.2.4
Copilot Feb 8, 2026
7967560
Update yarn.lock for react-dom version change
Copilot Feb 8, 2026
81feeb8
Load PolicyManager in SettingsManager.Initialize() to fix reset setti…
Copilot Feb 8, 2026
ceb5a64
Move PolicyManager.Load() to App.xaml.cs to avoid duplicate calls
Copilot Feb 8, 2026
8d40a49
Chore: Refactoring & dotnet format
BornToBeRoot Feb 8, 2026
5664c46
Update MainWindow.xaml.cs
BornToBeRoot Feb 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Source/NETworkManager.Localization/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3960,4 +3960,7 @@ If you click Cancel, the profile file will remain unencrypted.</value>
<data name="HelpMessage_MaximumNumberOfBackups" xml:space="preserve">
<value>Number of backups that are retained before the oldest one is deleted.</value>
</data>
<data name="SettingManagedByAdministrator" xml:space="preserve">
<value>This setting is managed by your administrator.</value>
</data>
</root>
6 changes: 3 additions & 3 deletions Source/NETworkManager.Models/Network/NetworkInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public sealed class NetworkInterface
"Npcap Packet Driver (NPCAP)",
"QoS Packet Scheduler",
"WFP 802.3 MAC Layer LightWeight Filter",
"Ethernet (Kerneldebugger)",
"Ethernet (Kerneldebugger)",
"Filter Driver",
"WAN Miniport",
"Microsoft Wi-Fi Direct Virtual Adapter"
Expand Down Expand Up @@ -84,8 +84,8 @@ public static List<NetworkInterfaceInfo> GetNetworkInterfaces()
// Filter out virtual/filter adapters introduced in .NET 9/10
// Check if the adapter name or description contains any filtered pattern
// See: https://github.com/dotnet/runtime/issues/122751
if (NetworkInterfaceFilteredPatterns.Any(pattern =>
networkInterface.Name.Contains(pattern) ||
if (NetworkInterfaceFilteredPatterns.Any(pattern =>
networkInterface.Name.Contains(pattern) ||
networkInterface.Description.Contains(pattern)))
continue;

Expand Down
22 changes: 11 additions & 11 deletions Source/NETworkManager.Profiles/ProfileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ private static void Load(ProfileFileInfo profileFileInfo)

if (loadedProfileUpdated)
LoadedProfileFileChanged(LoadedProfileFile, true);

// Notify subscribers that profiles have been loaded/updated
ProfilesUpdated(false);
}
Expand Down Expand Up @@ -994,7 +994,7 @@ private static void AddGroups(List<GroupInfo> groups, bool profilesChanged = tru

ProfilesUpdated(profilesChanged);
}

/// <summary>
/// Method to add a <see cref="GroupInfo" /> to the loaded profile data.
/// </summary>
Expand Down Expand Up @@ -1022,7 +1022,7 @@ public static GroupInfo GetGroupByName(string name)


var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name.Equals(name));

if (group == null)
throw new InvalidOperationException($"Group '{name}' not found.");

Expand Down Expand Up @@ -1089,12 +1089,12 @@ public static bool GroupExists(string name)
public static bool IsGroupEmpty(string name)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);

var group = LoadedProfileFileData.Groups.FirstOrDefault(x => x.Name == name);

if (group == null)
throw new InvalidOperationException($"Group '{name}' not found.");

return group.Profiles.Count == 0;
}

Expand All @@ -1118,7 +1118,7 @@ public static void AddProfile(ProfileInfo profile)
AddGroup(new GroupInfo(profile.Group));

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.");

Expand All @@ -1144,7 +1144,7 @@ public static void ReplaceProfile(ProfileInfo oldProfile, ProfileInfo 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.");

Expand All @@ -1155,7 +1155,7 @@ public static void ReplaceProfile(ProfileInfo oldProfile, ProfileInfo newProfile
AddGroup(new GroupInfo(newProfile.Group));

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.");

Expand All @@ -1177,7 +1177,7 @@ public static void RemoveProfile(ProfileInfo 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.");

Expand Down Expand Up @@ -1205,7 +1205,7 @@ public static void RemoveProfiles(IEnumerable<ProfileInfo> profiles)
}

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 ?? "<unnamed>"}'.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public static class GlobalStaticConfiguration
// Settings: Settings
public static bool Settings_IsDailyBackupEnabled => true;
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";
Expand Down
3 changes: 3 additions & 0 deletions Source/NETworkManager.Settings/NETworkManager.Settings.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
<Compile Include="..\GlobalAssemblyInfo.cs" Link="Properties\GlobalAssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="config.json.example">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Themes\Dark.Accent1.xaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<SubType>Designer</SubType>
Expand Down
13 changes: 13 additions & 0 deletions Source/NETworkManager.Settings/PolicyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;

namespace NETworkManager.Settings;

/// <summary>
/// Class that represents system-wide policies that override user settings.
/// This configuration is loaded from a config.json file in the application directory.
/// </summary>
public class PolicyInfo
{
[JsonPropertyName("Update_CheckForUpdatesAtStartup")]
public bool? Update_CheckForUpdatesAtStartup { get; set; }
}
102 changes: 102 additions & 0 deletions Source/NETworkManager.Settings/PolicyManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using log4net;
using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace NETworkManager.Settings;

/// <summary>
/// Manager for system-wide policies that are loaded from a config.json file
/// in the application directory. These policies override user settings.
/// </summary>
public static class PolicyManager
{
#region Variables

/// <summary>
/// Logger for logging.
/// </summary>
private static readonly ILog Log = LogManager.GetLogger(typeof(PolicyManager));

/// <summary>
/// Config file name.
/// </summary>
private static string ConfigFileName => "config.json";

/// <summary>
/// System-wide policies that are currently loaded.
/// </summary>
public static PolicyInfo Current { get; private set; }

/// <summary>
/// JSON serializer options for consistent serialization/deserialization.
/// </summary>
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() }
};

#endregion

#region Methods

/// <summary>
/// Method to get the config file path in the application directory.
/// </summary>
/// <returns>Config file path.</returns>
private static string GetConfigFilePath()
{
return Path.Combine(AssemblyManager.Current.Location, ConfigFileName);
}

/// <summary>
/// Method to load the system-wide policies from config.json file in the application directory.
/// </summary>
public static void Load()
{
var filePath = GetConfigFilePath();

// Check if config file exists
if (File.Exists(filePath))
{
try
{
Log.Info($"Loading system-wide policies from: {filePath}");

var jsonString = File.ReadAllText(filePath);

// Treat empty or JSON "null" as "no policies" instead of crashing
if (string.IsNullOrWhiteSpace(jsonString))
{
Current = new PolicyInfo();
Log.Info("Config file is empty, no system-wide policies loaded.");
}
else
{
Current = JsonSerializer.Deserialize<PolicyInfo>(jsonString, JsonOptions) ?? new PolicyInfo();

Log.Info("System-wide policies loaded successfully.");

// Log enabled settings
Log.Info($"System-wide policy - Update_CheckForUpdatesAtStartup: {Current.Update_CheckForUpdatesAtStartup?.ToString() ?? "Not set"}");
}
}
catch (Exception ex)
{
Log.Error($"Failed to load system-wide policies from: {filePath}", ex);
Current = new PolicyInfo();
}
}
else
{
Log.Debug($"No system-wide policy file found at: {filePath}");
Current = new PolicyInfo();
}
}

#endregion
}
4 changes: 2 additions & 2 deletions Source/NETworkManager.Settings/SettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ private static SettingsInfo DeserializeFromXmlFile(string filePath)
/// Method to save the currently loaded settings (to a file).
/// </summary>
public static void Save()
{
{
// Create the directory if it does not exist
Directory.CreateDirectory(GetSettingsFolderLocation());

Expand Down Expand Up @@ -282,7 +282,7 @@ private static void CreateDailyBackupIfNeeded()
// Create backup if needed
var currentDate = DateTime.Now.Date;
var lastBackupDate = Current.LastBackup.Date;

if (lastBackupDate < currentDate)
{
Log.Info("Creating daily backup of settings...");
Expand Down
3 changes: 3 additions & 0 deletions Source/NETworkManager.Settings/config.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"Update_CheckForUpdatesAtStartup": false
}
Loading