diff --git a/Source/GlobalAssemblyInfo.cs b/Source/GlobalAssemblyInfo.cs
index b557cda1cd..90884159c7 100644
--- a/Source/GlobalAssemblyInfo.cs
+++ b/Source/GlobalAssemblyInfo.cs
@@ -6,5 +6,5 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-[assembly: AssemblyVersion("2025.1.18.0")]
-[assembly: AssemblyFileVersion("2025.1.18.0")]
+[assembly: AssemblyVersion("2025.3.16.0")]
+[assembly: AssemblyFileVersion("2025.3.16.0")]
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index 783c4482cd..e074ddeb6b 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -771,6 +771,15 @@ public static string ApplicationName_DNSLookup {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die Hosts File Editor ähnelt.
+ ///
+ public static string ApplicationName_HostsFileEditor {
+ get {
+ return ResourceManager.GetString("ApplicationName_HostsFileEditor", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die HTTP Headers ähnelt.
///
@@ -1761,6 +1770,15 @@ public static string CommandLineArguments {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die Comment ähnelt.
+ ///
+ public static string Comment {
+ get {
+ return ResourceManager.GetString("Comment", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Community ähnelt.
///
@@ -4532,6 +4550,24 @@ public static string Hosts {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die Hosts File Editor ähnelt.
+ ///
+ public static string HostsFileEditor {
+ get {
+ return ResourceManager.GetString("HostsFileEditor", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die To edit the hosts file, the application must be started with elevated rights! ähnelt.
+ ///
+ public static string HostsFileEditorAdminMessage {
+ get {
+ return ResourceManager.GetString("HostsFileEditorAdminMessage", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die HotKeys ähnelt.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index b568f36d54..acd237281f 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -3879,4 +3879,16 @@ Right-click for more options.
Profile file
+
+ Hosts File Editor
+
+
+ Hosts File Editor
+
+
+ To edit the hosts file, the application must be started with elevated rights!
+
+
+ Comment
+
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/AWS/AWSProfile.cs b/Source/NETworkManager.Models/AWS/AWSProfile.cs
index cc4240e64b..0fcdc08377 100644
--- a/Source/NETworkManager.Models/AWS/AWSProfile.cs
+++ b/Source/NETworkManager.Models/AWS/AWSProfile.cs
@@ -6,10 +6,10 @@ public static class AWSProfile
{
public static List GetDefaultList()
{
- return new List
- {
- new(false, "default", "eu-central-1"),
- new(false, "default", "us-east-1")
- };
+ return
+ [
+ new AWSProfileInfo(false, "default", "eu-central-1"),
+ new AWSProfileInfo(false, "default", "us-east-1")
+ ];
}
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/AWS/AWSProfileInfo.cs b/Source/NETworkManager.Models/AWS/AWSProfileInfo.cs
index c10d98202e..e453f8a70b 100644
--- a/Source/NETworkManager.Models/AWS/AWSProfileInfo.cs
+++ b/Source/NETworkManager.Models/AWS/AWSProfileInfo.cs
@@ -1,7 +1,7 @@
namespace NETworkManager.Models.AWS;
///
-/// Class is used to store informations about an AWS profile.
+/// Class is used to store information about an AWS profile.
///
public class AWSProfileInfo
{
@@ -15,7 +15,7 @@ public AWSProfileInfo()
///
/// Create an instance of with parameters.
///
- /// .
+ /// .
/// .
/// .
public AWSProfileInfo(bool isEnabled, string profile, string region)
diff --git a/Source/NETworkManager.Models/ApplicationManager.cs b/Source/NETworkManager.Models/ApplicationManager.cs
index 072dacd875..c179b5d774 100644
--- a/Source/NETworkManager.Models/ApplicationManager.cs
+++ b/Source/NETworkManager.Models/ApplicationManager.cs
@@ -1,8 +1,8 @@
-using System;
+using MahApps.Metro.IconPacks;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
-using MahApps.Metro.IconPacks;
namespace NETworkManager.Models;
@@ -92,6 +92,9 @@ public static Canvas GetIcon(ApplicationName name)
case ApplicationName.SNTPLookup:
canvas.Children.Add(new PackIconMaterial { Kind = PackIconMaterialKind.ClockCheckOutline });
break;
+ case ApplicationName.HostsFileEditor:
+ canvas.Children.Add(new PackIconMaterial { Kind = PackIconMaterialKind.FileEditOutline });
+ break;
case ApplicationName.DiscoveryProtocol:
canvas.Children.Add(new PackIconMaterial { Kind = PackIconMaterialKind.SwapHorizontal });
break;
diff --git a/Source/NETworkManager.Models/ApplicationName.cs b/Source/NETworkManager.Models/ApplicationName.cs
index 63f0665cbb..29196133fb 100644
--- a/Source/NETworkManager.Models/ApplicationName.cs
+++ b/Source/NETworkManager.Models/ApplicationName.cs
@@ -90,6 +90,11 @@ public enum ApplicationName
///
SNTPLookup,
+ ///
+ /// Hosts file editor application.
+ ///
+ HostsFileEditor,
+
///
/// Discovery protocol application.
///
diff --git a/Source/NETworkManager.Models/Export/ExportManager.HostsFileEntry.cs b/Source/NETworkManager.Models/Export/ExportManager.HostsFileEntry.cs
new file mode 100644
index 0000000000..2eb7ae3e58
--- /dev/null
+++ b/Source/NETworkManager.Models/Export/ExportManager.HostsFileEntry.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using NETworkManager.Models.HostsFileEditor;
+using Newtonsoft.Json;
+
+namespace NETworkManager.Models.Export;
+
+public static partial class ExportManager
+{
+ ///
+ /// Method to export objects from type to a file.
+ ///
+ /// Path to the export file.
+ /// Allowed are CSV, XML or JSON.
+ /// Objects as to export.
+ public static void Export(string filePath, ExportFileType fileType, IReadOnlyList collection)
+ {
+ switch (fileType)
+ {
+ case ExportFileType.Csv:
+ CreateCsv(collection, filePath);
+ break;
+ case ExportFileType.Xml:
+ CreateXml(collection, filePath);
+ break;
+ case ExportFileType.Json:
+ CreateJson(collection, filePath);
+ break;
+ case ExportFileType.Txt:
+ default:
+ throw new ArgumentOutOfRangeException(nameof(fileType), fileType, null);
+ }
+ }
+
+ ///
+ /// Creates a CSV file from the given collection.
+ ///
+ /// Objects as to export.
+ /// Path to the export file.
+ private static void CreateCsv(IEnumerable collection, string filePath)
+ {
+ var stringBuilder = new StringBuilder();
+
+ stringBuilder.AppendLine(
+ $"{nameof(HostsFileEntry.IsEnabled)},{nameof(HostsFileEntry.IPAddress)},{nameof(HostsFileEntry.Hostname)},{nameof(HostsFileEntry.Comment)}");
+
+ foreach (var info in collection)
+ stringBuilder.AppendLine($"{info.IsEnabled},{info.IPAddress},{info.Hostname},{info.Comment}");
+
+ File.WriteAllText(filePath, stringBuilder.ToString());
+ }
+
+ ///
+ /// Creates a XML file from the given collection.
+ ///
+ /// Objects as to export.
+ /// Path to the export file.
+ private static void CreateXml(IEnumerable collection, string filePath)
+ {
+ var document = new XDocument(DefaultXDeclaration,
+ new XElement(ApplicationName.HostsFileEditor.ToString(),
+ new XElement(nameof(HostsFileEntry) + "s",
+ from info in collection
+ select
+ new XElement(nameof(HostsFileEntry),
+ new XElement(nameof(HostsFileEntry.IsEnabled), info.IsEnabled),
+ new XElement(nameof(HostsFileEntry.IPAddress), info.IPAddress),
+ new XElement(nameof(HostsFileEntry.Hostname), info.Hostname),
+ new XElement(nameof(HostsFileEntry.Comment), info.Comment)))));
+
+ document.Save(filePath);
+ }
+
+ ///
+ /// Creates a JSON file from the given collection.
+ ///
+ /// Objects as to export.
+ /// Path to the export file.
+ private static void CreateJson(IReadOnlyList collection, string filePath)
+ {
+ var jsonData = new object[collection.Count];
+
+ for (var i = 0; i < collection.Count; i++)
+ jsonData[i] = new
+ {
+ collection[i].IsEnabled,
+ collection[i].IPAddress,
+ collection[i].Hostname,
+ collection[i].Comment
+ };
+
+ File.WriteAllText(filePath, JsonConvert.SerializeObject(jsonData, Formatting.Indented));
+ }
+}
diff --git a/Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs b/Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs
new file mode 100644
index 0000000000..7f487aa89c
--- /dev/null
+++ b/Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using log4net;
+using NETworkManager.Utilities;
+
+namespace NETworkManager.Models.HostsFileEditor;
+
+public static class HostsFileEditor
+{
+#region Events
+ public static event EventHandler HostsFileChanged;
+
+ private static void OnHostsFileChanged()
+ {
+ Log.Debug("OnHostsFileChanged - Hosts file changed.");
+ HostsFileChanged?.Invoke(null, EventArgs.Empty);
+ }
+ #endregion
+
+ #region Variables
+ private static readonly ILog Log = LogManager.GetLogger(typeof(HostsFileEditor));
+
+ private static readonly FileSystemWatcher HostsFileWatcher;
+
+ ///
+ /// Path to the hosts file.
+ ///
+ private static string HostsFilePath => Path.Combine(Environment.SystemDirectory, "drivers", "etc", "hosts");
+
+ ///
+ /// Example values in the hosts file that should be ignored.
+ ///
+ private static readonly HashSet<(string IPAddress, string Hostname)> ExampleValuesToIgnore =
+ [
+ ("102.54.94.97", "rhino.acme.com"),
+ ("38.25.63.10", "x.acme.com")
+ ];
+
+ ///
+ /// Regex to match a hosts file entry with optional comments, supporting IPv4, IPv6, and hostnames
+ ///
+ private static readonly Regex HostsFileEntryRegex = new(RegexHelper.HostsEntryRegex);
+
+ #endregion
+
+ #region Constructor
+
+ static HostsFileEditor()
+ {
+ // Create a file system watcher to monitor changes to the hosts file
+ try
+ {
+ Log.Debug("HostsFileEditor - Creating file system watcher for hosts file...");
+
+ // Create the file system watcher
+ HostsFileWatcher = new FileSystemWatcher();
+ HostsFileWatcher.Path = Path.GetDirectoryName(HostsFilePath) ?? throw new InvalidOperationException("Hosts file path is invalid.");
+ HostsFileWatcher.Filter = Path.GetFileName(HostsFilePath) ?? throw new InvalidOperationException("Hosts file name is invalid.");
+ HostsFileWatcher.NotifyFilter = NotifyFilters.LastWrite;
+
+ // Maybe fired twice. This is a known bug/feature.
+ // See: https://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice
+ HostsFileWatcher.Changed += (_, _) => OnHostsFileChanged();
+
+ // Enable the file system watcher
+ HostsFileWatcher.EnableRaisingEvents = true;
+
+ Log.Debug("HostsFileEditor - File system watcher for hosts file created.");
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Failed to create file system watcher for hosts file.", ex);
+ }
+ }
+ #endregion
+
+ #region Methods
+ public static Task> GetHostsFileEntriesAsync()
+ {
+ return Task.Run(GetHostsFileEntries);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ private static IEnumerable GetHostsFileEntries()
+ {
+ var hostsFileLines = File.ReadAllLines(HostsFilePath);
+
+ // Parse the hosts file content
+ var entries = new List();
+
+ foreach (var line in hostsFileLines)
+ {
+ var result = HostsFileEntryRegex.Match(line.Trim());
+
+ if (result.Success)
+ {
+ Log.Debug("GetHostsFileEntries - Line matched: " + line);
+
+ var entry = new HostsFileEntry
+ {
+ IsEnabled = !result.Groups[1].Value.Equals("#"),
+ IPAddress = result.Groups[2].Value,
+ Hostname = result.Groups[3].Value.Replace(@"\s", "").Trim(),
+ Comment = result.Groups[4].Value.TrimStart('#',' '),
+ Line = line
+ };
+
+ // Skip example entries
+ if(!entry.IsEnabled)
+ {
+ if (ExampleValuesToIgnore.Contains((entry.IPAddress, entry.Hostname)))
+ {
+ Log.Debug("GetHostsFileEntries - Matched example entry. Skipping...");
+ continue;
+ }
+ }
+
+ entries.Add(entry);
+ }
+ else
+ {
+ Log.Debug("GetHostsFileEntries - Line not matched: " + line);
+ }
+ }
+
+ return entries;
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/Source/NETworkManager.Models/HostsFileEditor/HostsFileEntry.cs b/Source/NETworkManager.Models/HostsFileEditor/HostsFileEntry.cs
new file mode 100644
index 0000000000..eecb1d7da1
--- /dev/null
+++ b/Source/NETworkManager.Models/HostsFileEditor/HostsFileEntry.cs
@@ -0,0 +1,69 @@
+namespace NETworkManager.Models.HostsFileEditor;
+
+///
+/// Class that represents a single entry in the hosts file.
+///
+public class HostsFileEntry
+{
+ ///
+ /// Indicates whether the entry is enabled or not.
+ ///
+ public bool IsEnabled { get; init; }
+
+ ///
+ /// IP address of the host.
+ ///
+ public string IPAddress { get; init; }
+
+ ///
+ /// Host name(s) of the host. Multiple host names are separated by a
+ /// space (equal to the hosts file format).
+ ///
+ public string Hostname { get; init; }
+
+ ///
+ /// Comment of the host.
+ ///
+ public string Comment { get; init; }
+
+ ///
+ /// Line of the entry in the hosts file.
+ ///
+ public string Line { get; init; }
+
+ ///
+ /// Creates a new instance of .
+ ///
+ public HostsFileEntry()
+ {
+
+ }
+
+ ///
+ /// Creates a new instance of with parameters.
+ ///
+ /// Indicates whether the entry is enabled or not.
+ /// IP address of the host.
+ /// Host name(s) of the host.
+ /// Comment of the host.
+ public HostsFileEntry(bool isEnabled, string ipAddress, string hostname, string comment)
+ {
+ IsEnabled = isEnabled;
+ IPAddress = ipAddress;
+ Hostname = hostname;
+ Comment = comment;
+ }
+
+ ///
+ /// Creates a new instance of with parameters.
+ ///
+ /// Indicates whether the entry is enabled or not.
+ /// IP address of the host.
+ /// Host name(s) of the host.
+ /// Comment of the host.
+ /// Line of the entry in the hosts file.
+ public HostsFileEntry(bool isEnabled, string ipAddress, string hostname, string comment, string line) : this(isEnabled, ipAddress, hostname, comment)
+ {
+ Line = line;
+ }
+}
diff --git a/Source/NETworkManager.Models/Lookup/OUILookup.cs b/Source/NETworkManager.Models/Lookup/OUILookup.cs
index 3763d4e712..20cc8e4654 100644
--- a/Source/NETworkManager.Models/Lookup/OUILookup.cs
+++ b/Source/NETworkManager.Models/Lookup/OUILookup.cs
@@ -22,7 +22,7 @@ public static class OUILookup
///
static OUILookup()
{
- OUIInfoList = new List();
+ OUIInfoList = [];
var document = new XmlDocument();
document.Load(OuiFilePath);
diff --git a/Source/NETworkManager.Models/Lookup/PortLookup.cs b/Source/NETworkManager.Models/Lookup/PortLookup.cs
index c031a407b4..cef648b1cc 100644
--- a/Source/NETworkManager.Models/Lookup/PortLookup.cs
+++ b/Source/NETworkManager.Models/Lookup/PortLookup.cs
@@ -19,7 +19,7 @@ public static class PortLookup
///
static PortLookup()
{
- PortList = new List();
+ PortList = [];
var document = new XmlDocument();
document.Load(PortsFilePath);
diff --git a/Source/NETworkManager.Models/Network/Connection.cs b/Source/NETworkManager.Models/Network/Connection.cs
index 24e3c270a0..866772d0a1 100644
--- a/Source/NETworkManager.Models/Network/Connection.cs
+++ b/Source/NETworkManager.Models/Network/Connection.cs
@@ -52,7 +52,7 @@ public enum TcpTableClass
}
// Cache for remote host names with some default values
- private static readonly Dictionary _remoteHostNames = new()
+ private static readonly Dictionary RemoteHostNames = new()
{
{ IPAddress.Parse("127.0.0.1"), "localhost" },
{ IPAddress.Parse("::1"), "localhost" },
@@ -94,9 +94,9 @@ private static List GetActiveTcpConnections()
var row = (MibTcpRowOwnerPid)Marshal.PtrToStructure(rowPtr, typeof(MibTcpRowOwnerPid))!;
var localAddress = new IPAddress(row.localAddr);
- var localPort = BitConverter.ToUInt16(new[] { row.localPort2, row.localPort1 }, 0);
+ var localPort = BitConverter.ToUInt16([row.localPort2, row.localPort1], 0);
var remoteAddress = new IPAddress(row.remoteAddr);
- var remotePort = BitConverter.ToUInt16(new[] { row.remotePort2, row.remotePort1 }, 0);
+ var remotePort = BitConverter.ToUInt16([row.remotePort2, row.remotePort1], 0);
var state = (TcpState)row.state;
// Get process info by PID
@@ -116,14 +116,14 @@ private static List GetActiveTcpConnections()
}
// Resolve remote host name if not cached
- if (!_remoteHostNames.ContainsKey(remoteAddress))
+ if (!RemoteHostNames.ContainsKey(remoteAddress))
{
var dnsResolverTask = DNSClient.GetInstance().ResolvePtrAsync(remoteAddress);
dnsResolverTask.Wait();
// Cache the result
- _remoteHostNames.Add(remoteAddress,
+ RemoteHostNames.Add(remoteAddress,
!dnsResolverTask.Result.HasError ? dnsResolverTask.Result.Value : "-/-");
}
@@ -133,7 +133,7 @@ private static List GetActiveTcpConnections()
localPort,
remoteAddress,
remotePort,
- _remoteHostNames.GetValueOrDefault(remoteAddress, "-/-"),
+ RemoteHostNames.GetValueOrDefault(remoteAddress, "-/-"),
state,
processId,
processName,
diff --git a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
index c7c07ce381..56fb4704f6 100644
--- a/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
+++ b/Source/NETworkManager.Settings/GlobalStaticConfiguration.cs
@@ -19,12 +19,15 @@ public static class GlobalStaticConfiguration
{
#region Global settings
+ // Application config
+ public static int ApplicationUIRefreshInterval => 2500;
+
// Type to search (average type speed --> 187 chars/min)
public static TimeSpan SearchDispatcherTimerTimeSpan => new(0, 0, 0, 0, 750);
// Network config
public static int NetworkChangeDetectionDelay => 5000;
-
+
// Profile config
public static bool Profile_ExpandProfileView => true;
public static double Profile_WidthCollapsed => 40;
@@ -219,6 +222,9 @@ public static class GlobalStaticConfiguration
// Application: SNTP Lookup
public static int SNTPLookup_Timeout => 4000;
public static ExportFileType SNTPLookup_ExportFileType => ExportFileType.Csv;
+
+ // Application: Hosts File Editor
+ public static ExportFileType HostsFileEditor_ExportFileType => ExportFileType.Csv;
// Application: Discovery Protocol
public static DiscoveryProtocol DiscoveryProtocol_Protocol => DiscoveryProtocol.LldpCdp;
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index 4c997c92eb..52db1bcc32 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -3584,7 +3584,7 @@ public ExportFileType SNMP_ExportFileType
#region SNTP Lookup
- private ObservableCollection _sntpLookup_SNTPServers = new();
+ private ObservableCollection _sntpLookup_SNTPServers = [];
public ObservableCollection SNTPLookup_SNTPServers
{
@@ -3660,6 +3660,40 @@ public ExportFileType SNTPLookup_ExportFileType
}
#endregion
+
+ #region Hosts File Editor
+
+ private string _hostsFileEditor_ExportFilePath;
+
+ public string HostsFileEditor_ExportFilePath
+ {
+ get => _hostsFileEditor_ExportFilePath;
+ set
+ {
+ if (value == _hostsFileEditor_ExportFilePath)
+ return;
+
+ _hostsFileEditor_ExportFilePath = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private ExportFileType _hostsFileEditor_ExportFileType = GlobalStaticConfiguration.HostsFileEditor_ExportFileType;
+
+ public ExportFileType HostsFileEditor_ExportFileType
+ {
+ get => _hostsFileEditor_ExportFileType;
+ set
+ {
+ if (value == _hostsFileEditor_ExportFileType)
+ return;
+
+ _hostsFileEditor_ExportFileType = value;
+ OnPropertyChanged();
+ }
+ }
+
+ #endregion
#region Discovery Protocol
diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs
index 83056b5df5..9fee3564fc 100644
--- a/Source/NETworkManager.Settings/SettingsManager.cs
+++ b/Source/NETworkManager.Settings/SettingsManager.cs
@@ -1,13 +1,10 @@
-using System;
-using System.Collections.ObjectModel;
+using log4net;
+using NETworkManager.Models;
+using NETworkManager.Models.Network;
+using System;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
-using log4net;
-using NETworkManager.Controls;
-using NETworkManager.Models;
-using NETworkManager.Models.Network;
-using NETworkManager.Models.PowerShell;
namespace NETworkManager.Settings;
@@ -189,7 +186,7 @@ public static void Upgrade(Version fromVersion, Version toVersion)
if (fromVersion < new Version(2023, 11, 28, 0))
UpgradeTo_2023_11_28_0();
-
+
// 2024.11.11.0
if (fromVersion < new Version(2024, 11, 11, 0))
UpgradeTo_2024_11_11_0();
@@ -205,7 +202,7 @@ public static void Upgrade(Version fromVersion, Version toVersion)
Log.Info("Settings upgrade finished!");
}
- ///
+ ///
/// Method to apply changes for version 2023.3.7.0.
///
private static void UpgradeTo_2023_3_7_0()
@@ -217,14 +214,14 @@ private static void UpgradeTo_2023_3_7_0()
Current.General_ApplicationList.Add(ApplicationManager.GetDefaultList()
.First(x => x.Name == ApplicationName.SNTPLookup));
Current.SNTPLookup_SNTPServers =
- new ObservableCollection(SNTPServer.GetDefaultList());
+ [.. SNTPServer.GetDefaultList()];
// Add IP Scanner custom commands
foreach (var customCommand in from customCommand in IPScannerCustomCommand.GetDefaultList()
- let customCommandFound =
- Current.IPScanner_CustomCommands.FirstOrDefault(x => x.Name == customCommand.Name)
- where customCommandFound == null
- select customCommand)
+ let customCommandFound =
+ Current.IPScanner_CustomCommands.FirstOrDefault(x => x.Name == customCommand.Name)
+ where customCommandFound == null
+ select customCommand)
{
Log.Info($"Add \"{customCommand.Name}\" to \"IPScanner_CustomCommands\"...");
Current.IPScanner_CustomCommands.Add(customCommand);
@@ -251,7 +248,7 @@ private static void UpgradeTo_2023_3_7_0()
// Add new DNS lookup profiles
Log.Info("Init \"DNSLookup_DNSServers_v2\" with default DNS servers...");
Current.DNSLookup_DNSServers =
- new ObservableCollection(DNSServer.GetDefaultList());
+ [.. DNSServer.GetDefaultList()];
}
///
@@ -263,7 +260,7 @@ private static void UpgradeTo_2023_4_26_0()
// Add SNMP OID profiles
Log.Info("Add SNMP OID profiles...");
- Current.SNMP_OidProfiles = new ObservableCollection(SNMPOIDProfile.GetDefaultList());
+ Current.SNMP_OidProfiles = [.. SNMPOIDProfile.GetDefaultList()];
}
///
@@ -297,9 +294,9 @@ private static void UpgradeTo_2023_11_28_0()
// Add DNS lookup profiles after refactoring
Log.Info("Init \"DNSLookup_DNSServers\" with default DNS servers...");
Current.DNSLookup_DNSServers =
- new ObservableCollection(DNSServer.GetDefaultList());
+ [.. DNSServer.GetDefaultList()];
}
-
+
///
/// Method to apply changes for version 2024.11.11.0.
///
@@ -309,7 +306,7 @@ private static void UpgradeTo_2024_11_11_0()
Log.Info("Reset ApplicationList to default...");
Current.General_ApplicationList =
- new ObservableSetCollection(ApplicationManager.GetDefaultList());
+ [.. ApplicationManager.GetDefaultList()];
}
///
@@ -319,6 +316,13 @@ private static void UpgradeTo_2024_11_11_0()
private static void UpgradeToLatest(Version version)
{
Log.Info($"Apply upgrade to {version}...");
+
+ // Add Hosts editor application
+ Log.Info("Add new app \"Hosts File Editor\"...");
+
+ Current.General_ApplicationList.Insert(
+ ApplicationManager.GetDefaultList().ToList().FindIndex(x => x.Name == ApplicationName.HostsFileEditor),
+ ApplicationManager.GetDefaultList().First(x => x.Name == ApplicationName.HostsFileEditor));
}
#endregion
diff --git a/Source/NETworkManager.Utilities/RegexHelper.cs b/Source/NETworkManager.Utilities/RegexHelper.cs
index 60a4d50e63..127d2f98f1 100644
--- a/Source/NETworkManager.Utilities/RegexHelper.cs
+++ b/Source/NETworkManager.Utilities/RegexHelper.cs
@@ -111,4 +111,20 @@ public static class RegexHelper
// Match an SNMP OID (like 1.3.6.1 or .1.3.6.2)
public const string SnmpOidRegex = @"^\.?[012]\.(?:[0-9]|[1-3][0-9])(\.\d+)*$";
+
+ // Match a hosts file entry with optional comments, supporting IPv4, IPv6, and hostnames
+ // ^* : Matches the beginning of the line
+ // (#)? : Optionally matches a comment (#) at the start of the line
+ // \s* : Matches any whitespace after the comment (or before the IP)
+ // ((?:(?:\d{1,3}\.){3}\d{1,3}) : Matches an IPv4 address (e.g., 192.168.1.1)
+ // | : OR (alternation between IPv4 and IPv6)
+ // (?:(?:[A-Fa-f0-9:]+:+)+[A-Fa-f0-9]+) : Matches an IPv6 address (e.g., 2001:db8::1)
+ // \s+ : Matches one or more spaces between the IP and the hostnames
+ // ([\w.-]+(?:\s+[\w.-]+)*) : Matches one or more hostnames, separated by spaces
+ // \s* : Matches optional whitespace after hostnames
+ // (#.*)? : Optionally matches a comment after hostnames
+ // $ : Anchors the match to the end of the line
+ public static string HostsEntryRegex =>
+ @"^(#)?\s*((?:(?:\d{1,3}\.){3}\d{1,3})|(?:(?:[A-Fa-f0-9:]+:+)+[A-Fa-f0-9]+))\s+([\w.-]+(?:\s+[\w.-]+)*)\s*(#.*)?$";
+
}
\ No newline at end of file
diff --git a/Source/NETworkManager/MainWindow.xaml.cs b/Source/NETworkManager/MainWindow.xaml.cs
index bb012051d6..96e54a37ca 100644
--- a/Source/NETworkManager/MainWindow.xaml.cs
+++ b/Source/NETworkManager/MainWindow.xaml.cs
@@ -674,6 +674,7 @@ private void LoadApplicationList()
private WebConsoleHostView _webConsoleHostView;
private SNMPHostView _snmpHostView;
private SNTPLookupHostView _sntpLookupHostView;
+ private HostsFileEditorView _hostsFileEditorView;
private DiscoveryProtocolView _discoveryProtocolView;
private WakeOnLANView _wakeOnLanView;
private SubnetCalculatorHostView _subnetCalculatorHostView;
@@ -824,6 +825,14 @@ private void OnApplicationViewVisible(ApplicationName name, bool fromSettings =
ContentControlApplication.Content = _sntpLookupHostView;
break;
+ case ApplicationName.HostsFileEditor:
+ if(_hostsFileEditorView == null)
+ _hostsFileEditorView = new HostsFileEditorView();
+ else
+ _hostsFileEditorView.OnViewVisible();
+
+ ContentControlApplication.Content = _hostsFileEditorView;
+ break;
case ApplicationName.DiscoveryProtocol:
if (_discoveryProtocolView == null)
_discoveryProtocolView = new DiscoveryProtocolView();
@@ -904,6 +913,10 @@ private void OnApplicationViewVisible(ApplicationName name, bool fromSettings =
ContentControlApplication.Content = _arpTableView;
break;
+
+ default:
+ Log.Error("Cannot show unknown application view: " + name);
+ break;
}
}
@@ -959,6 +972,9 @@ private void OnApplicationViewHide(ApplicationName name)
case ApplicationName.SNTPLookup:
_sntpLookupHostView?.OnViewHide();
break;
+ case ApplicationName.HostsFileEditor:
+ _hostsFileEditorView?.OnViewHide();
+ break;
case ApplicationName.DiscoveryProtocol:
_discoveryProtocolView?.OnViewHide();
break;
@@ -989,6 +1005,9 @@ private void OnApplicationViewHide(ApplicationName name)
case ApplicationName.ARPTable:
_arpTableView?.OnViewHide();
break;
+ default:
+ Log.Error("Cannot hide unknown application view: " + name);
+ break;
}
}
diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj
index c479f18a79..a64d71b66d 100644
--- a/Source/NETworkManager/NETworkManager.csproj
+++ b/Source/NETworkManager/NETworkManager.csproj
@@ -136,6 +136,11 @@
Wpf
Designer
+
+ MSBuild:Compile
+ Wpf
+ Designer
+
diff --git a/Source/NETworkManager/ViewModels/ARPTableViewModel.cs b/Source/NETworkManager/ViewModels/ARPTableViewModel.cs
index f2944390c2..dd03b2b545 100644
--- a/Source/NETworkManager/ViewModels/ARPTableViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ARPTableViewModel.cs
@@ -38,11 +38,11 @@ public ARPTableViewModel(IDialogCoordinator instance)
ResultsView.Filter = o =>
{
- if (o is not ARPInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ARPInfo info)
+ return false;
// Search by IPAddress and MACAddress
return info.IPAddress.ToString().IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
@@ -95,7 +95,7 @@ public string Search
}
}
- private ObservableCollection _results = new();
+ private ObservableCollection _results = [];
public ObservableCollection Results
{
@@ -429,8 +429,8 @@ private async Task Refresh()
IsRefreshing = true;
Results.Clear();
-
- (await ARP.GetTableAsync()).ForEach(x => Results.Add(x));
+
+ (await ARP.GetTableAsync()).ForEach(Results.Add);
IsRefreshing = false;
}
diff --git a/Source/NETworkManager/ViewModels/AWSSessionManagerHostViewModel.cs b/Source/NETworkManager/ViewModels/AWSSessionManagerHostViewModel.cs
index 7b77c82db8..5bc0474e38 100644
--- a/Source/NETworkManager/ViewModels/AWSSessionManagerHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/AWSSessionManagerHostViewModel.cs
@@ -650,7 +650,7 @@ private async Task SyncAllInstanceIDsFromAWS()
}
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
- await Task.Delay(2000);
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
Log.Info("All Instance IDs synced from AWS!");
@@ -679,7 +679,7 @@ private async Task SyncGroupInstanceIDsFromAWS(string group)
}
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
- await Task.Delay(2000);
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
Log.Info("Group synced!");
@@ -998,12 +998,12 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
+ if (string.IsNullOrEmpty(Search))
+ return true ;
+
if (o is not ProfileInfo info)
return false;
- if (string.IsNullOrEmpty(Search))
- return true;
-
var search = Search.Trim();
// Search by: Tag=xxx (exact match, ignore case)
diff --git a/Source/NETworkManager/ViewModels/AboutViewModel.cs b/Source/NETworkManager/ViewModels/AboutViewModel.cs
index c4b1913122..4642b17f5d 100644
--- a/Source/NETworkManager/ViewModels/AboutViewModel.cs
+++ b/Source/NETworkManager/ViewModels/AboutViewModel.cs
@@ -42,7 +42,7 @@ private async Task CheckForUpdatesAsync()
IsUpdateCheckRunning = true;
// Show a loading animation for the user
- await Task.Delay(1000);
+ await Task.Delay(1250);
var updater = new Updater();
diff --git a/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs b/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs
index 420296ebb9..c12096632b 100644
--- a/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ConnectionsViewModel.cs
@@ -39,12 +39,12 @@ public ConnectionsViewModel(IDialogCoordinator instance)
IPAddressHelper.CompareIPAddresses(x.LocalIPAddress, y.LocalIPAddress));
ResultsView.Filter = o =>
- {
- if (o is not ConnectionInfo info)
- return false;
-
+ {
if (string.IsNullOrEmpty(Search))
- return true;
+ return true;
+
+ if (o is not ConnectionInfo info)
+ return false;
// Search by local/remote IP Address, local/remote Port, Protocol and State
return info.LocalIPAddress.ToString().IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
@@ -346,8 +346,8 @@ private async Task Refresh()
IsRefreshing = true;
Results.Clear();
-
- (await Connection.GetActiveTcpConnectionsAsync()).ForEach(x => Results.Add(x));
+
+ (await Connection.GetActiveTcpConnectionsAsync()).ForEach(Results.Add);
IsRefreshing = false;
}
diff --git a/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs b/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs
index 14d5b7aba1..8ef01da9d7 100644
--- a/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs
@@ -372,11 +372,13 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
+ if (string.IsNullOrEmpty(Search))
+ return true;
+
if (o is not ProfileInfo info)
return false;
- if (string.IsNullOrEmpty(Search))
- return true;
+
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
new file mode 100644
index 0000000000..cb6fd94c40
--- /dev/null
+++ b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs
@@ -0,0 +1,330 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Timers;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Threading;
+using log4net;
+using MahApps.Metro.Controls;
+using MahApps.Metro.Controls.Dialogs;
+using NETworkManager.Localization.Resources;
+using NETworkManager.Models.Export;
+using NETworkManager.Models.HostsFileEditor;
+using NETworkManager.Models.Network;
+using NETworkManager.Settings;
+using NETworkManager.Utilities;
+using NETworkManager.Views;
+
+namespace NETworkManager.ViewModels;
+
+public class HostsFileEditorViewModel : ViewModelBase
+{
+ #region Variables
+ private static readonly ILog Log = LogManager.GetLogger(typeof(HostsFileEditorViewModel));
+
+ private readonly IDialogCoordinator _dialogCoordinator;
+
+ private readonly bool _isLoading;
+
+ private string _search;
+ public string Search
+ {
+ get => _search;
+ set
+ {
+ if (value == _search)
+ return;
+
+ _search = value;
+
+ ResultsView.Refresh();
+
+ OnPropertyChanged();
+ }
+ }
+
+ private ObservableCollection _results = [];
+
+ public ObservableCollection Results
+ {
+ get => _results;
+ set
+ {
+ if (value == _results)
+ return;
+
+ _results = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public ICollectionView ResultsView { get; }
+
+ private HostsFileEntry _selectedResult;
+
+ public HostsFileEntry SelectedResult
+ {
+ get => _selectedResult;
+ set
+ {
+ if (value == _selectedResult)
+ return;
+
+ _selectedResult = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private IList _selectedResults = new ArrayList();
+
+ public IList SelectedResults
+ {
+ get => _selectedResults;
+ set
+ {
+ if (Equals(value, _selectedResults))
+ return;
+
+ _selectedResults = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private bool _isRefreshing;
+
+ public bool IsRefreshing
+ {
+ get => _isRefreshing;
+ set
+ {
+ if (value == _isRefreshing)
+ return;
+
+ _isRefreshing = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private bool _isStatusMessageDisplayed;
+
+ public bool IsStatusMessageDisplayed
+ {
+ get => _isStatusMessageDisplayed;
+ set
+ {
+ if (value == _isStatusMessageDisplayed)
+ return;
+
+ _isStatusMessageDisplayed = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private string _statusMessage;
+
+ public string StatusMessage
+ {
+ get => _statusMessage;
+ private set
+ {
+ if (value == _statusMessage)
+ return;
+
+ _statusMessage = value;
+ OnPropertyChanged();
+ }
+ }
+
+ #endregion
+
+ #region Constructor, LoadSettings
+
+ public HostsFileEditorViewModel(IDialogCoordinator instance)
+ {
+ _isLoading = true;
+ _dialogCoordinator = instance;
+
+ // Result view + search
+ ResultsView = CollectionViewSource.GetDefaultView(Results);
+ ResultsView.Filter = o =>
+ {
+ if (string.IsNullOrEmpty(Search))
+ return true;
+
+ if (o is not HostsFileEntry entry)
+ return false;
+
+ return entry.IPAddress.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
+ entry.Hostname.IndexOf(Search, StringComparison.OrdinalIgnoreCase)> -1 ||
+ entry.Comment.IndexOf(Search, StringComparison.OrdinalIgnoreCase)> -1;
+ };
+
+ // Get hosts file entries
+ Refresh(true).ConfigureAwait(false);
+
+ // Watch hosts file for changes
+ HostsFileEditor.HostsFileChanged += (_, _) =>
+ {
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ Refresh().ConfigureAwait(false);
+ });
+ };
+
+ _isLoading = false;
+ }
+
+ private void LoadSettings()
+ {
+
+ }
+
+ #endregion
+
+ #region ICommands & Actions
+ public ICommand RefreshCommand => new RelayCommand(_ => RefreshAction().ConfigureAwait(false), Refresh_CanExecute);
+
+ private bool Refresh_CanExecute(object parameter)
+ {
+ return Application.Current.MainWindow != null &&
+ !((MetroWindow)Application.Current.MainWindow).IsAnyDialogOpen &&
+ !ConfigurationManager.Current.IsChildWindowOpen;
+ }
+
+ private async Task RefreshAction()
+ {
+ await Refresh();
+ }
+
+ public ICommand ExportCommand => new RelayCommand(_ => ExportAction().ConfigureAwait(false));
+
+ private async Task ExportAction()
+ {
+ var customDialog = new CustomDialog
+ {
+ Title = Strings.Export
+ };
+
+ var exportViewModel = new ExportViewModel(async instance =>
+ {
+ await _dialogCoordinator.HideMetroDialogAsync(this, customDialog);
+
+ try
+ {
+ ExportManager.Export(instance.FilePath, instance.FileType,
+ instance.ExportAll
+ ? Results
+ : new ObservableCollection(SelectedResults.Cast().ToArray()));
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while exporting data as " + instance.FileType, ex);
+
+ var settings = AppearanceManager.MetroDialog;
+ settings.AffirmativeButtonText = Strings.OK;
+
+ await _dialogCoordinator.ShowMessageAsync(this, Strings.Error,
+ Strings.AnErrorOccurredWhileExportingTheData + Environment.NewLine +
+ Environment.NewLine + ex.Message, MessageDialogStyle.Affirmative, settings);
+ }
+
+ SettingsManager.Current.HostsFileEditor_ExportFileType = instance.FileType;
+ SettingsManager.Current.HostsFileEditor_ExportFilePath = instance.FilePath;
+ }, _ => { _dialogCoordinator.HideMetroDialogAsync(this, customDialog); }, [
+ ExportFileType.Csv, ExportFileType.Xml, ExportFileType.Json
+ ], true, SettingsManager.Current.HostsFileEditor_ExportFileType, SettingsManager.Current.HostsFileEditor_ExportFilePath);
+
+ customDialog.Content = new ExportDialog
+ {
+ DataContext = exportViewModel
+ };
+
+ await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog);
+ }
+
+ public ICommand RestartAsAdminCommand => new RelayCommand(_ => RestartAsAdminAction().ConfigureAwait(false));
+
+ private async Task RestartAsAdminAction()
+ {
+ try
+ {
+ (Application.Current.MainWindow as MainWindow)?.RestartApplication(true);
+ }
+ catch (Exception ex)
+ {
+ await _dialogCoordinator.ShowMessageAsync(this, Strings.Error, ex.Message,
+ MessageDialogStyle.Affirmative, AppearanceManager.MetroDialog);
+ }
+ }
+ #endregion
+
+ #region Methods
+
+ private async Task Refresh(bool init = false)
+ {
+ if(IsRefreshing)
+ return;
+
+ IsRefreshing = true;
+
+ // Retry 3 times if the hosts file is locked
+ for (var i = 1; i < 4; i++)
+ {
+ // Wait for 2.5 seconds on refresh
+ if (init == false || i > 1)
+ {
+ StatusMessage = "Refreshing...";
+ IsStatusMessageDisplayed = true;
+
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
+ }
+
+ try
+ {
+ var entries = await HostsFileEditor.GetHostsFileEntriesAsync();
+
+ Results.Clear();
+
+ entries.ToList().ForEach(Results.Add);
+
+ StatusMessage = "Reloaded at " + DateTime.Now.ToShortTimeString();
+ IsStatusMessageDisplayed = true;
+
+ break;
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex);
+
+ StatusMessage = "Failed to reload hosts file: " + ex.Message;
+
+ if (i < 3)
+ StatusMessage += Environment.NewLine + "Retrying in 2.5 seconds...";
+
+ IsStatusMessageDisplayed = true;
+
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
+ }
+ }
+
+ IsRefreshing = false;
+ }
+
+ public void OnViewVisible()
+ {
+
+ }
+
+ public void OnViewHide()
+ {
+
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs b/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs
index 027847f1d6..fea55efac4 100644
--- a/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPApiDNSResolverWidgetViewModel.cs
@@ -88,7 +88,7 @@ private async Task CheckAsync()
Result = null;
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
- await Task.Delay(2000);
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
Result = await DNSResolverService.GetInstance().GetDNSResolverAsync();
diff --git a/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs b/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs
index 5f68a2c463..144f0ebd79 100644
--- a/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPApiIPGeolocationWidgetViewModel.cs
@@ -90,7 +90,7 @@ private async Task CheckAsync()
Result = null;
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
- await Task.Delay(2000);
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
Result = await IPGeolocationService.GetInstance().GetIPGeolocationAsync();
diff --git a/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs b/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs
index 11cbed1e30..b0bb7f42eb 100644
--- a/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPGeolocationHostViewModel.cs
@@ -373,11 +373,11 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
- return true;
+ return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs b/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs
index 54896a8470..a352707c3a 100644
--- a/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/IPScannerHostViewModel.cs
@@ -377,12 +377,12 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
+ if (string.IsNullOrEmpty(Search))
+ return true;
+
if (o is not ProfileInfo info)
return false;
- if (string.IsNullOrEmpty(Search))
- return true;
-
var search = Search.Trim();
// Search by: Tag=xxx (exact match, ignore case)
diff --git a/Source/NETworkManager/ViewModels/ListenersViewModel.cs b/Source/NETworkManager/ViewModels/ListenersViewModel.cs
index 2fbb0c11e4..5b7ecb8643 100644
--- a/Source/NETworkManager/ViewModels/ListenersViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ListenersViewModel.cs
@@ -39,12 +39,12 @@ public ListenersViewModel(IDialogCoordinator instance)
ResultsView.Filter = o =>
{
+ if (string.IsNullOrEmpty(Search))
+ return true;
+
if (o is not ListenerInfo info)
return false;
- if (string.IsNullOrEmpty(Search))
- return true;
-
// Search by IP Address, Port and Protocol
return info.IPAddress.ToString().IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
info.Port.ToString().IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
@@ -335,8 +335,8 @@ private async Task Refresh()
IsRefreshing = true;
Results.Clear();
-
- (await Listener.GetAllActiveListenersAsync()).ForEach(x => Results.Add(x));
+
+ (await Listener.GetAllActiveListenersAsync()).ForEach(Results.Add);
IsRefreshing = false;
}
diff --git a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
index 752a3b008e..f11c4ecf84 100644
--- a/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
+++ b/Source/NETworkManager/ViewModels/NetworkInterfaceViewModel.cs
@@ -938,8 +938,6 @@ private async Task RemoveIPv4AddressAction()
private async void ReloadNetworkInterfaces()
{
- Debug.WriteLine("ReloadNetworkInterfaces.............");
-
// Avoid multiple reloads
if(IsNetworkInterfaceLoading)
return;
@@ -947,7 +945,7 @@ private async void ReloadNetworkInterfaces()
IsNetworkInterfaceLoading = true;
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
- await Task.Delay(2000);
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
// Store the last selected id
var id = SelectedNetworkInterface?.Id ?? string.Empty;
@@ -1337,12 +1335,12 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
- return true;
+ return true;
+ if (o is not ProfileInfo info)
+ return false;
+
var search = Search.Trim();
// Search by: Tag=xxx (exact match, ignore case)
diff --git a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs
index 2cbb4a41ca..413f213ebb 100644
--- a/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PingMonitorHostViewModel.cs
@@ -594,11 +594,11 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs b/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs
index ec1f7ac912..bac9aa7a66 100644
--- a/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PortScannerHostViewModel.cs
@@ -378,12 +378,12 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
+ if (string.IsNullOrEmpty(Search))
+ return true;
+
if (o is not ProfileInfo info)
return false;
- if (string.IsNullOrEmpty(Search))
- return true;
-
var search = Search.Trim();
// Search by: Tag=xxx (exact match, ignore case)
diff --git a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
index 15e1b1b0f9..7165e98b05 100644
--- a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
@@ -637,11 +637,11 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/ProfilesViewModel.cs b/Source/NETworkManager/ViewModels/ProfilesViewModel.cs
index 86f21862ab..e71def69ba 100644
--- a/Source/NETworkManager/ViewModels/ProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/ProfilesViewModel.cs
@@ -264,11 +264,11 @@ private void SetProfilesView(GroupInfo group, ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs b/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs
index c40c225d7a..e560490cf1 100644
--- a/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PuTTYHostViewModel.cs
@@ -714,11 +714,11 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs b/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs
index 06b8e44942..9995bbf22c 100644
--- a/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/RemoteDesktopHostViewModel.cs
@@ -586,11 +586,11 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs b/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs
index d606bb5230..d624478b5b 100644
--- a/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SNMPHostViewModel.cs
@@ -393,11 +393,11 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs b/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs
index f7274df5db..4b17303ac0 100644
--- a/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsAutostartViewModel.cs
@@ -47,7 +47,7 @@ private async Task EnableDisableAutostart(bool enable)
await AutostartManager.DisableAsync();
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
- await Task.Delay(2000);
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
}
catch (Exception ex)
{
diff --git a/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs b/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs
index 98904dc020..ef6213c807 100644
--- a/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsLanguageViewModel.cs
@@ -27,7 +27,7 @@ public SettingsLanguageViewModel()
if (string.IsNullOrEmpty(Search))
return true;
- if (!(o is LocalizationInfo info))
+ if (o is not LocalizationInfo info)
return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs b/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs
index b93eb53032..9240e006f6 100644
--- a/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/TigerVNCHostViewModel.cs
@@ -512,13 +512,12 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.GroupDescriptions.Add(new PropertyGroupDescription(nameof(ProfileInfo.Group)));
Profiles.Filter = o =>
- {
+ {if (string.IsNullOrEmpty(Search))
+ return true;
+
if (o is not ProfileInfo info)
return false;
- if (string.IsNullOrEmpty(Search))
- return true;
-
var search = Search.Trim();
// Search by: Tag=xxx (exact match, ignore case)
diff --git a/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs b/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs
index c43a0b1760..dee7c15f94 100644
--- a/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/TracerouteHostViewModel.cs
@@ -378,11 +378,11 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs b/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs
index 6ff7c1e246..03175c4d35 100644
--- a/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WakeOnLANViewModel.cs
@@ -369,7 +369,7 @@ private async Task WakeUp(WakeOnLANInfo info)
WakeOnLAN.Send(info);
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
- await Task.Delay(2000);
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
StatusMessage = Strings.MagicPacketSentMessage;
IsStatusMessageDisplayed = true;
@@ -464,11 +464,11 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs b/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs
index b8a672cd76..59dbf9c47e 100644
--- a/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WebConsoleHostViewModel.cs
@@ -482,11 +482,11 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs b/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs
index 66690ca5ea..68351a70ef 100644
--- a/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WhoisHostViewModel.cs
@@ -372,11 +372,11 @@ private void SetProfilesView(ProfileInfo profile = null)
Profiles.Filter = o =>
{
- if (o is not ProfileInfo info)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not ProfileInfo info)
+ return false;
var search = Search.Trim();
diff --git a/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs b/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs
index df40ca1548..90891b99d3 100644
--- a/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WiFiConnectViewModel.cs
@@ -345,13 +345,13 @@ public async Task CheckWpsAsync()
IsWpsChecking = true;
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
- await Task.Delay(1000);
+ await Task.Delay(1250);
IsWpsAvailable =
await WiFi.IsWpsAvailable(Options.AdapterInfo.WiFiAdapter, Options.NetworkInfo.AvailableNetwork);
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
- await Task.Delay(1000);
+ await Task.Delay(1250);
IsWpsChecking = false;
}
diff --git a/Source/NETworkManager/ViewModels/WiFiViewModel.cs b/Source/NETworkManager/ViewModels/WiFiViewModel.cs
index 8d6c713639..961bc46b32 100644
--- a/Source/NETworkManager/ViewModels/WiFiViewModel.cs
+++ b/Source/NETworkManager/ViewModels/WiFiViewModel.cs
@@ -471,29 +471,28 @@ public WiFiViewModel(IDialogCoordinator instance)
ListSortDirection.Ascending));
NetworksView.Filter = o =>
{
- if (o is not WiFiNetworkInfo info)
- return false;
-
- if (info.Radio == WiFiRadio.GHz2dot4 && !Show2dot4GHzNetworks)
- return false;
-
- if (info.Radio == WiFiRadio.GHz5 && !Show5GHzNetworks)
- return false;
-
- if (info.Radio == WiFiRadio.GHz6 && !Show6GHzNetworks)
- return false;
-
if (string.IsNullOrEmpty(Search))
return true;
+
+ if (o is not WiFiNetworkInfo info)
+ return false;
- // Search by: SSID, Security, Frequency , Channel, BSSID (MAC address), Vendor, Phy kind
- return info.AvailableNetwork.Ssid.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
- info.NetworkAuthenticationType.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
- $"{info.ChannelCenterFrequencyInGigahertz}".IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
- $"{info.Channel}".IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
- info.AvailableNetwork.Bssid.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
- info.Vendor.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
- info.PhyKind.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1;
+ switch (info.Radio)
+ {
+ case WiFiRadio.GHz2dot4 when !Show2dot4GHzNetworks:
+ case WiFiRadio.GHz5 when !Show5GHzNetworks:
+ case WiFiRadio.GHz6 when !Show6GHzNetworks:
+ return false;
+ default:
+ // Search by: SSID, Security, Frequency , Channel, BSSID (MAC address), Vendor, Phy kind
+ return info.AvailableNetwork.Ssid.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
+ info.NetworkAuthenticationType.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
+ $"{info.ChannelCenterFrequencyInGigahertz}".IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
+ $"{info.Channel}".IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
+ info.AvailableNetwork.Bssid.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
+ info.Vendor.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
+ info.PhyKind.IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1;
+ }
};
// Load network adapters
@@ -599,7 +598,7 @@ private async Task LoadAdaptersAsync(string adapterId = null)
IsAdaptersLoading = true;
// Show a loading animation for the user
- await Task.Delay(2500);
+ await Task.Delay(GlobalStaticConfiguration.ApplicationUIRefreshInterval);
try
{
@@ -645,7 +644,7 @@ private async Task LoadAdaptersAsync(string adapterId = null)
Log.Debug("LoadAdaptersAsync - Done.");
}
- private async Task ScanAsync(WiFiAdapterInfo adapterInfo, bool refreshing = false, uint delayInMs = 0)
+ private async Task ScanAsync(WiFiAdapterInfo adapterInfo, bool refreshing = false, int delayInMs = 0)
{
Log.Debug($"ScanAsync - Scanning WiFi adapter \"{adapterInfo.NetworkInterfaceInfo.Name}\" with delay of {delayInMs} ms...");
@@ -661,7 +660,7 @@ private async Task ScanAsync(WiFiAdapterInfo adapterInfo, bool refreshing = fals
}
if (delayInMs != 0)
- await Task.Delay((int)delayInMs);
+ await Task.Delay(delayInMs);
var statusMessage = string.Empty;
@@ -922,7 +921,7 @@ private async void Disconnect()
}
// Refresh
- await ScanAsync(SelectedAdapter, true, 2500);
+ await ScanAsync(SelectedAdapter, true, GlobalStaticConfiguration.ApplicationUIRefreshInterval);
}
private async Task Export()
diff --git a/Source/NETworkManager/Views/ARPTableView.xaml b/Source/NETworkManager/Views/ARPTableView.xaml
index fb5b342a0d..26b2a1aff0 100644
--- a/Source/NETworkManager/Views/ARPTableView.xaml
+++ b/Source/NETworkManager/Views/ARPTableView.xaml
@@ -38,8 +38,8 @@
+ Width="250" Text="{Binding Path=Search, UpdateSourceTrigger=PropertyChanged}"
+ Style="{StaticResource ResourceKey=SearchTextBox}" />
+
+
-
+
-
-
-
-
-
-
-
-
-
-
+
@@ -124,11 +125,11 @@
MinWidth="100" />
-
-
+
@@ -141,7 +142,7 @@
-
+
diff --git a/Source/NETworkManager/Views/HostsFileEditorView.xaml b/Source/NETworkManager/Views/HostsFileEditorView.xaml
new file mode 100644
index 0000000000..9faf7fec04
--- /dev/null
+++ b/Source/NETworkManager/Views/HostsFileEditorView.xaml
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/NETworkManager/Views/HostsFileEditorView.xaml.cs b/Source/NETworkManager/Views/HostsFileEditorView.xaml.cs
new file mode 100644
index 0000000000..ef1a2a8c63
--- /dev/null
+++ b/Source/NETworkManager/Views/HostsFileEditorView.xaml.cs
@@ -0,0 +1,33 @@
+using System.Windows;
+using System.Windows.Controls;
+using MahApps.Metro.Controls.Dialogs;
+using NETworkManager.ViewModels;
+
+namespace NETworkManager.Views;
+
+public partial class HostsFileEditorView
+{
+ private readonly HostsFileEditorViewModel _viewModel = new(DialogCoordinator.Instance);
+
+ public HostsFileEditorView()
+ {
+ InitializeComponent();
+ DataContext = _viewModel;
+ }
+
+ public void OnViewHide()
+ {
+ _viewModel.OnViewHide();
+ }
+
+ public void OnViewVisible()
+ {
+ _viewModel.OnViewVisible();
+ }
+
+ private void ContextMenu_Opened(object sender, RoutedEventArgs e)
+ {
+ if (sender is ContextMenu menu)
+ menu.DataContext = _viewModel;
+ }
+}
diff --git a/Source/NETworkManager/Views/WiFiView.xaml b/Source/NETworkManager/Views/WiFiView.xaml
index 8e7a6cdb36..a28ecd7dce 100644
--- a/Source/NETworkManager/Views/WiFiView.xaml
+++ b/Source/NETworkManager/Views/WiFiView.xaml
@@ -774,7 +774,6 @@
Style="{DynamicResource ResourceKey=LoadingIndicatorPulseStyle}"
Visibility="{Binding Path=IsBackgroundSearchRunning, Converter={StaticResource ResourceKey=BooleanToVisibilityCollapsedConverter}}"
Width="24" Height="24"
- VerticalAlignment="Center"
SpeedRatio="1"
Margin="0,0,10,0" />
-
+
+